diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml index bd9ba70..4422983 100644 --- a/findbugs-exclude-filter.xml +++ b/findbugs-exclude-filter.xml @@ -1,4 +1,4 @@ - + - - - + - + - - + + + + + + + - - diff --git a/pom.xml b/pom.xml index add131d..59c91e6 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.ehcache sizeof - 0.4.1-SNAPSHOT + 0.5-SNAPSHOT jar Ehcache SizeOf Engine @@ -40,6 +40,11 @@ + + net.bytebuddy + byte-buddy-agent + 1.12.9 + org.slf4j slf4j-api @@ -64,9 +69,9 @@ test - org.ow2.asm - asm - 6.0 + net.bytebuddy + byte-buddy + 1.12.9 test @@ -92,7 +97,7 @@ maven-failsafe-plugin - 2.20.1 + 2.22.2 maven-gpg-plugin @@ -129,9 +134,9 @@ - org.codehaus.mojo - findbugs-maven-plugin - 3.0.5 + com.github.spotbugs + spotbugs-maven-plugin + 4.7.0.0 org.jacoco @@ -188,7 +193,6 @@ - src/hidden/** README.adoc **/*.txt @@ -196,44 +200,6 @@ - - org.codehaus.gmavenplus - gmavenplus-plugin - - - create-agent-jar - process-classes - - execute - - - - - - - - - - - org.codehaus.groovy - groovy-all - - 2.4.12 - runtime - - - maven-surefire-plugin @@ -275,8 +241,8 @@ - org.codehaus.mojo - findbugs-maven-plugin + com.github.spotbugs + spotbugs-maven-plugin ${basedir}/findbugs-exclude-filter.xml @@ -309,6 +275,31 @@ + + + java9 + + [9,) + + + + + maven-surefire-plugin + + -Xms64m -Xmx64m --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED + + + + maven-failsafe-plugin + + -Xms64m -Xmx64m --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED + + + + + + + https://github.com/ehcache/sizeof scm:git:https://github.com/ehcache/sizeof.git diff --git a/src/hidden/resources/META-INF/MANIFEST.MF b/src/hidden/resources/META-INF/MANIFEST.MF deleted file mode 100644 index 0621893..0000000 --- a/src/hidden/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Agent-Class: org.ehcache.sizeof.impl.SizeOfAgent -Premain-Class: org.ehcache.sizeof.impl.SizeOfAgent diff --git a/src/main/java/org/ehcache/sizeof/ObjectGraphWalker.java b/src/main/java/org/ehcache/sizeof/ObjectGraphWalker.java index 4491549..2108006 100644 --- a/src/main/java/org/ehcache/sizeof/ObjectGraphWalker.java +++ b/src/main/java/org/ehcache/sizeof/ObjectGraphWalker.java @@ -15,6 +15,8 @@ */ package org.ehcache.sizeof; +import org.ehcache.sizeof.util.UnsafeAccess; +import sun.misc.Unsafe; import org.ehcache.sizeof.filters.SizeOfFilter; import org.ehcache.sizeof.util.WeakIdentityConcurrentMap; import org.slf4j.Logger; @@ -30,6 +32,7 @@ import java.util.Deque; import java.util.IdentityHashMap; import java.util.Set; +import java.util.function.Function; import static java.util.Collections.newSetFromMap; @@ -44,7 +47,7 @@ final class ObjectGraphWalker { private static final String VERBOSE_DEBUG_LOGGING = "org.ehcache.sizeof.verboseDebugLogging"; private static final boolean USE_VERBOSE_DEBUG_LOGGING; - private final WeakIdentityConcurrentMap, SoftReference>> fieldCache = + private final WeakIdentityConcurrentMap, SoftReference>>> fieldCache = new WeakIdentityConcurrentMap<>(); private final WeakIdentityConcurrentMap, Boolean> classCache = new WeakIdentityConcurrentMap<>(); @@ -156,12 +159,8 @@ long walk(VisitorListener visitorListener, Object... root) { nullSafeAdd(toVisit, Array.get(ref, i)); } } else { - for (Field field : getFilteredFields(refClass)) { - try { - nullSafeAdd(toVisit, field.get(ref)); - } catch (IllegalAccessException ex) { - throw new RuntimeException(ex); - } + for (Function accessor : getFilteredFields(refClass)) { + nullSafeAdd(toVisit, accessor.apply(ref)); } } @@ -194,23 +193,66 @@ long walk(VisitorListener visitorListener, Object... root) { * @param refClass the type * @return A collection of fields to be visited */ - private Collection getFilteredFields(Class refClass) { - SoftReference> ref = fieldCache.get(refClass); - Collection fieldList = ref != null ? ref.get() : null; + private Collection> getFilteredFields(Class refClass) { + SoftReference>> ref = fieldCache.get(refClass); + Collection> fieldList = ref != null ? ref.get() : null; if (fieldList != null) { return fieldList; } else { - Collection result; - result = sizeOfFilter.filterFields(refClass, getAllFields(refClass)); + Collection fields = sizeOfFilter.filterFields(refClass, getAllFields(refClass)); + Collection> accessors = new ArrayList<>(fields.size()); if (USE_VERBOSE_DEBUG_LOGGING && LOG.isDebugEnabled()) { - for (Field field : result) { + for (Field field : fields) { if (Modifier.isTransient(field.getModifiers())) { LOG.debug("SizeOf engine walking transient field '{}' of class {}", field.getName(), refClass.getName()); } } } - fieldCache.put(refClass, new SoftReference<>(result)); - return result; + + for (Field field : fields) { + try { + try { + accessors.add(readUsingReflection(field)); + } catch (Throwable t) { + Function unsafeRead = readUsingUnsafe(field); + if (unsafeRead == null) { + throw t; + } else { + accessors.add(unsafeRead); + } + } + } catch (SecurityException e) { + LOG.error("Security settings prevent Ehcache from accessing the subgraph beneath '{}'" + + " - cache sizes may be underestimated as a result", field, e); + } catch (Throwable e) { + LOG.warn("The JVM is preventing Ehcache from accessing the subgraph beneath '{}'" + + " - cache sizes may be underestimated as a result", field, e); + } + } + + fieldCache.put(refClass, new SoftReference<>(accessors)); + return accessors; + } + } + + private static Function readUsingReflection(Field field) throws SecurityException { + field.setAccessible(true); + return ref -> { + try { + return field.get(ref); + } catch (IllegalAccessException e) { + throw new AssertionError("IllegalAccessException after setting accessible!", e); + } + }; + } + + private static Function readUsingUnsafe(Field field) { + Unsafe unsafe = UnsafeAccess.getUnsafe(); + if (unsafe == null) { + return null; + } else { + long offset = unsafe.objectFieldOffset(field); + return ref -> unsafe.getObject(ref, offset); } } @@ -241,17 +283,6 @@ private static Collection getAllFields(Class refClass) { for (Field field : klazz.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers()) && !field.getType().isPrimitive()) { - try { - field.setAccessible(true); - } catch (SecurityException e) { - LOG.error("Security settings prevent Ehcache from accessing the subgraph beneath '{}'" + - " - cache sizes may be underestimated as a result", field, e); - continue; - } catch (RuntimeException e) { - LOG.warn("The JVM is preventing Ehcache from accessing the subgraph beneath '{}'" + - " - cache sizes may be underestimated as a result", field, e); - continue; - } fields.add(field); } } diff --git a/src/main/java/org/ehcache/sizeof/ObjectSizer.java b/src/main/java/org/ehcache/sizeof/ObjectSizer.java new file mode 100644 index 0000000..6d69975 --- /dev/null +++ b/src/main/java/org/ehcache/sizeof/ObjectSizer.java @@ -0,0 +1,21 @@ +/** + * Copyright Terracotta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehcache.sizeof; + +public interface ObjectSizer { + + long sizeOf(Object obj); +} diff --git a/src/main/java/org/ehcache/sizeof/SizeOf.java b/src/main/java/org/ehcache/sizeof/SizeOf.java index 5847feb..a38061a 100644 --- a/src/main/java/org/ehcache/sizeof/SizeOf.java +++ b/src/main/java/org/ehcache/sizeof/SizeOf.java @@ -17,10 +17,19 @@ import org.ehcache.sizeof.filters.CombinationSizeOfFilter; import org.ehcache.sizeof.filters.SizeOfFilter; -import org.ehcache.sizeof.impl.AgentSizeOf; -import org.ehcache.sizeof.impl.ReflectionSizeOf; -import org.ehcache.sizeof.impl.UnsafeSizeOf; +import org.ehcache.sizeof.impl.AgentSizer; +import org.ehcache.sizeof.impl.ReflectionSizer; +import org.ehcache.sizeof.impl.UnsafeSizer; import org.ehcache.sizeof.util.WeakIdentityConcurrentMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Objects; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static java.util.stream.Collectors.toList; /** * Abstract sizeOf for Java. It will rely on a proper sizeOf to measure sizes of entire object graphs @@ -79,20 +88,11 @@ public static SizeOf newInstance(final SizeOfFilter... filters) { } public static SizeOf newInstance(boolean bypassFlyweight, boolean cache, final SizeOfFilter... filters) { - final SizeOfFilter filter = new CombinationSizeOfFilter(filters); - try { - return new AgentSizeOf(filter, cache, bypassFlyweight); - } catch (UnsupportedOperationException e) { - try { - return new UnsafeSizeOf(filter, cache, bypassFlyweight); - } catch (UnsupportedOperationException f) { - try { - return new ReflectionSizeOf(filter, cache, bypassFlyweight); - } catch (UnsupportedOperationException g) { - throw new UnsupportedOperationException("A suitable SizeOf engine could not be loaded: " + e + ", " + f + ", " + g); - } - } - } + return new DelegatingSizeOf(new CombinationSizeOfFilter(filters), cache, bypassFlyweight, + AgentSizer::new, + UnsafeSizer::new, + ReflectionSizer::new + ); } /** @@ -133,4 +133,49 @@ public long visit(final Object object) { } } } + + private static class DelegatingSizeOf extends SizeOf { + + private static final Logger LOGGER = LoggerFactory.getLogger(DelegatingSizeOf.class); + + private final List sizers; + + /** + * Builds a new SizeOf that will filter fields according to the provided filter + * + * @param fieldFilter The filter to apply + * @param caching whether to cache reflected fields + * @param bypassFlyweight whether "Flyweight Objects" are to be ignored + * @see SizeOfFilter + */ + public DelegatingSizeOf(SizeOfFilter fieldFilter, boolean caching, boolean bypassFlyweight, Supplier... sizerFactories) { + super(fieldFilter, caching, bypassFlyweight); + this.sizers = Stream.of(sizerFactories).map(s -> { + try { + return s.get(); + } catch (Throwable t) { + LOGGER.warn("Cannot use object sizing " + s, t); + return null; + } + }).filter(Objects::nonNull).collect(toList()); + + if (sizers.isEmpty()) { + throw new UnsupportedOperationException("A suitable sizing engine could not be loaded"); + } + } + + @Override + public long sizeOf(Object obj) { + + for (ObjectSizer sizer : sizers) { + try { + return sizer.sizeOf(obj); + } catch (Throwable t) { + LOGGER.warn("Failed to size {} with {}", obj, sizer); + } + } + + throw new UnsupportedOperationException("Could not size " + obj); + } + } } diff --git a/src/main/java/org/ehcache/sizeof/annotations/AnnotationProxyFactory.java b/src/main/java/org/ehcache/sizeof/annotations/AnnotationProxyFactory.java index 1b68022..625a7e3 100644 --- a/src/main/java/org/ehcache/sizeof/annotations/AnnotationProxyFactory.java +++ b/src/main/java/org/ehcache/sizeof/annotations/AnnotationProxyFactory.java @@ -17,6 +17,7 @@ import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -25,20 +26,21 @@ * It can come handy when you want to allow the consumers of your library not to depend on your API because of the annotations, still allowing them to use the original annotation methods. *

* Example : - *

+ *

{@code
  * //getting a custom annotation from a class
  * my.Annotation customAnnotation = klazz.getAnnotation(my.Annotation.class);
- * //if this annotation is "similar" (duck-typing, same methods) to the reference one, I can get a proxy to it, whose type is the reference annotation
+ * //if this annotation is "similar" (duck-typing, same methods) to the reference one,
+ * //I can get a proxy to it, whose type is the reference annotation
  * ehcache.Annotation annotation = AnnotationProxyFactory.getAnnotationProxy(customAnnotation, ehcache.Annotation.class);
- * 

- * //so my library can apply the behavior when the default annotation is used * - * @author Anthony Dahanne + * //so my library can apply the behavior when the default annotation is used * @ehcache.Annotation(action="true") public class UserClass {} - *

- * //or when a custom one is used, since all calls to action() will be caught and redirected to the custom annotation action method, if it exists, + * + * //or when a custom one is used, since all calls to action() will be caught + * //and redirected to the custom annotation action method, if it exists, * //or fall back to the reference action method * @my.Annotation(action="true") public class UserClass {} + * }

*/ public final class AnnotationProxyFactory { @@ -48,7 +50,7 @@ private AnnotationProxyFactory() { } /** - * Returns a proxy on the customAnnotation, having the same type than the referenceAnnotation + * Returns a proxy on the customAnnotation, having the same type as the referenceAnnotation * * @param customAnnotation annotation proxied * @param referenceAnnotation type of the returned annotation @@ -72,7 +74,7 @@ public AnnotationInvocationHandler(Annotation customAnnotation) { this.customAnnotation = customAnnotation; } - public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { + public Object invoke(final Object proxy, final Method method, final Object[] args) throws InvocationTargetException, IllegalAccessException { //trying to call the method on the custom annotation, if it exists Method methodOnCustom = getMatchingMethodOnGivenAnnotation(method); if (methodOnCustom != null) { diff --git a/src/main/java/org/ehcache/sizeof/impl/AgentLoader.java b/src/main/java/org/ehcache/sizeof/impl/AgentLoader.java deleted file mode 100644 index 3c3642f..0000000 --- a/src/main/java/org/ehcache/sizeof/impl/AgentLoader.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.instrument.Instrumentation; -import java.lang.management.ManagementFactory; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.List; - -/** - * This will try to load the agent using the Attach API of JDK6. - * If you are on an older JDK (v5) you can still use the agent by adding the -javaagent:[pathTojar] to your VM - * startup script - * - * @author Alex Snaps - */ -final class AgentLoader { - - private static final Logger LOGGER = LoggerFactory.getLogger(AgentLoader.class); - - private static final String SIZEOF_AGENT_CLASSNAME = "org.ehcache.sizeof.impl.SizeOfAgent"; - private static final String VIRTUAL_MACHINE_CLASSNAME = "com.sun.tools.attach.VirtualMachine"; - private static final Method VIRTUAL_MACHINE_ATTACH; - private static final Method VIRTUAL_MACHINE_DETACH; - private static final Method VIRTUAL_MACHINE_LOAD_AGENT; - - private static volatile Instrumentation instrumentation; - - static final String INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME = "org.ehcache.sizeof.agent.instrumentation"; - - static { - Method attach = null; - Method detach = null; - Method loadAgent = null; - try { - Class virtualMachineClass = getVirtualMachineClass(); - attach = virtualMachineClass.getMethod("attach", String.class); - detach = virtualMachineClass.getMethod("detach"); - loadAgent = virtualMachineClass.getMethod("loadAgent", String.class); - } catch (Throwable e) { - LOGGER.info("Unavailable or unrecognised attach API : {}", e.toString()); - } - VIRTUAL_MACHINE_ATTACH = attach; - VIRTUAL_MACHINE_DETACH = detach; - VIRTUAL_MACHINE_LOAD_AGENT = loadAgent; - } - - private static Class getVirtualMachineClass() throws ClassNotFoundException { - try { - return AccessController.doPrivileged((PrivilegedExceptionAction>) () -> { - try { - return ClassLoader.getSystemClassLoader().loadClass(VIRTUAL_MACHINE_CLASSNAME); - } catch (ClassNotFoundException cnfe) { - for (File jar : getPossibleToolsJars()) { - try { - Class vmClass = new URLClassLoader(new URL[] { jar.toURI().toURL() }).loadClass(VIRTUAL_MACHINE_CLASSNAME); - LOGGER.info("Located valid 'tools.jar' at '{}'", jar); - return vmClass; - } catch (Throwable t) { - LOGGER.info("Exception while loading tools.jar from '{}': {}", jar, t); - } - } - throw new ClassNotFoundException(VIRTUAL_MACHINE_CLASSNAME); - } - }); - } catch (PrivilegedActionException pae) { - Throwable actual = pae.getCause(); - if (actual instanceof ClassNotFoundException) { - throw (ClassNotFoundException)actual; - } - throw new AssertionError("Unexpected checked exception : " + actual); - } - } - - private static List getPossibleToolsJars() { - List jars = new ArrayList<>(); - - File javaHome = new File(System.getProperty("java.home")); - File jreSourced = new File(javaHome, "lib/tools.jar"); - if (jreSourced.exists()) { - jars.add(jreSourced); - } - if ("jre".equals(javaHome.getName())) { - File jdkHome = new File(javaHome, "../"); - File jdkSourced = new File(jdkHome, "lib/tools.jar"); - if (jdkSourced.exists()) { - jars.add(jdkSourced); - } - } - return jars; - } - - /** - * Attempts to load the agent through the Attach API - * - * @return true if agent was loaded (which could have happened thought the -javaagent switch) - */ - static boolean loadAgent() { - synchronized (AgentLoader.class.getName().intern()) { - if (!agentIsAvailable() && VIRTUAL_MACHINE_LOAD_AGENT != null) { - try { - warnIfOSX(); - String name = ManagementFactory.getRuntimeMXBean().getName(); - Object vm = VIRTUAL_MACHINE_ATTACH.invoke(null, name.substring(0, name.indexOf('@'))); - try { - File agent = getAgentFile(); - LOGGER.info("Trying to load agent @ {}", agent); - if (agent != null) { - VIRTUAL_MACHINE_LOAD_AGENT.invoke(vm, agent.getAbsolutePath()); - } - } finally { - VIRTUAL_MACHINE_DETACH.invoke(vm); - } - } catch (InvocationTargetException ite) { - Throwable cause = ite.getCause(); - LOGGER.info("Failed to attach to VM and load the agent: {}: {}", cause.getClass(), cause.getMessage()); - } catch (Throwable t) { - LOGGER.info("Failed to attach to VM and load the agent: {}: {}", t.getClass(), t.getMessage()); - } - } - final boolean b = agentIsAvailable(); - if (b) { - LOGGER.info("Agent successfully loaded and available!"); - } - - return b; - } - } - - private static void warnIfOSX() { - if (JvmInformation.isOSX() && System.getProperty("java.io.tmpdir") != null) { - LOGGER.warn("Loading the SizeOfAgent will probably fail, as you are running on Apple OS X and have a value set for java.io.tmpdir\n" + - "They both result in a bug, not yet fixed by Apple, that won't let us attach to the VM and load the agent.\n" + - "Most probably, you'll also get a full thread-dump after this because of the failure... Nothing to worry about!\n" + - "You can bypass trying to load the Agent entirely by setting the System property '" - + AgentSizeOf.BYPASS_LOADING + "' to true"); - } - } - - private static File getAgentFile() throws IOException { - URL agent = AgentLoader.class.getResource("sizeof-agent.jar"); - if (agent == null) { - return null; - } else if (agent.getProtocol().equals("file")) { - return new File(agent.getFile()); - } else { - File temp = File.createTempFile("ehcache-sizeof-agent", ".jar"); - try (FileOutputStream fout = new FileOutputStream(temp); InputStream in = agent.openStream()) { - byte[] buffer = new byte[1024]; - while (true) { - int read = in.read(buffer); - if (read < 0) { - break; - } else { - fout.write(buffer, 0, read); - } - } - } finally { - temp.deleteOnExit(); - } - LOGGER.info("Extracted agent jar to temporary file {}", temp); - return temp; - } - } - - /** - * Checks whether the agent is available - * - * @return true if available - */ - static boolean agentIsAvailable() { - try { - if (instrumentation == null) { - instrumentation = (Instrumentation)System.getProperties().get(INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME); - } - if (instrumentation == null) { - Class sizeOfAgentClass = ClassLoader.getSystemClassLoader().loadClass(SIZEOF_AGENT_CLASSNAME); - Method getInstrumentationMethod = sizeOfAgentClass.getMethod("getInstrumentation"); - instrumentation = (Instrumentation)getInstrumentationMethod.invoke(sizeOfAgentClass); - } - return instrumentation != null; - } catch (SecurityException e) { - LOGGER.warn("Couldn't access the system classloader because of the security policies applied by " + - "the security manager. You either want to loosen these, so ClassLoader.getSystemClassLoader() and " + - "reflection API calls are permitted or the sizing will be done using some other mechanism.\n" + - "Alternatively, set the system property org.ehcache.sizeof.agent.instrumentationSystemProperty to true " + - "to have the agent put the required instances in the System Properties for the loader to access."); - return false; - } catch (Throwable e) { - return false; - } - } - - /** - * Returns the size of this Java object as calculated by the loaded agent. - * - * @param obj object to be sized - * @return size of the object in bytes - */ - static long agentSizeOf(Object obj) { - if (instrumentation == null) { - throw new UnsupportedOperationException("Sizeof agent is not available"); - } - return instrumentation.getObjectSize(obj); - } -} diff --git a/src/main/java/org/ehcache/sizeof/impl/AgentSizeOf.java b/src/main/java/org/ehcache/sizeof/impl/AgentSizeOf.java deleted file mode 100644 index c0965de..0000000 --- a/src/main/java/org/ehcache/sizeof/impl/AgentSizeOf.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import org.ehcache.sizeof.SizeOf; -import org.ehcache.sizeof.filters.SizeOfFilter; - -import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; - -/** - * SizeOf implementation that relies on a Java agent to be loaded to do the measurement - * It will try to load the agent through the JDK6 Attach API if available - * All it's constructor do throw UnsupportedOperationException if the agent isn't present or couldn't be loaded dynamically - * - * Inspired by Dr. Heinz Kabutz's Java Specialist Newsletter Issue #142 - * - * @author Chris Dennis - * @author Alex Snaps - * - * @link http://www.javaspecialists.eu/archive/Issue142.html - */ -public class AgentSizeOf extends SizeOf { - - /** - * System property name to bypass attaching to the VM and loading of Java agent to measure Object sizes. - */ - public static final String BYPASS_LOADING = "org.ehcache.sizeof.AgentSizeOf.bypass"; - - private static final boolean AGENT_LOADED = !Boolean.getBoolean(BYPASS_LOADING) && AgentLoader.loadAgent(); - - /** - * Builds a new SizeOf that will not filter fields and will cache reflected fields - * - * @throws UnsupportedOperationException If agent couldn't be loaded or isn't present - * @see #AgentSizeOf(SizeOfFilter, boolean, boolean) - */ - public AgentSizeOf() throws UnsupportedOperationException { - this(new PassThroughFilter()); - } - - /** - * Builds a new SizeOf that will filter fields according to the provided filter and will cache reflected fields - * - * @param filter The filter to apply - * @throws UnsupportedOperationException If agent couldn't be loaded or isn't present - * @see #AgentSizeOf(SizeOfFilter, boolean, boolean) - * @see org.ehcache.sizeof.filters.SizeOfFilter - */ - public AgentSizeOf(SizeOfFilter filter) throws UnsupportedOperationException { - this(filter, true, true); - } - - /** - * Builds a new SizeOf that will filter fields according to the provided filter - * - * @param filter The filter to apply - * @param caching whether to cache reflected fields - * @param bypassFlyweight whether "Flyweight Objects" are to be ignored - * @throws UnsupportedOperationException If agent couldn't be loaded or isn't present - * @see SizeOfFilter - */ - public AgentSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweight) throws UnsupportedOperationException { - super(filter, caching, bypassFlyweight); - if (!AGENT_LOADED) { - throw new UnsupportedOperationException("Agent not available or loadable"); - } - } - - @Override - public long sizeOf(Object obj) { - final long measuredSize = AgentLoader.agentSizeOf(obj); - return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), - measuredSize + CURRENT_JVM_INFORMATION.getAgentSizeOfAdjustment()); - } -} diff --git a/src/main/java/org/ehcache/sizeof/impl/AgentSizer.java b/src/main/java/org/ehcache/sizeof/impl/AgentSizer.java new file mode 100644 index 0000000..c70deab --- /dev/null +++ b/src/main/java/org/ehcache/sizeof/impl/AgentSizer.java @@ -0,0 +1,66 @@ +/** + * Copyright Terracotta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehcache.sizeof.impl; + +import net.bytebuddy.agent.ByteBuddyAgent; +import org.ehcache.sizeof.ObjectSizer; + +import java.lang.instrument.Instrumentation; + +import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; + +/** + * Object sizer that relies on a Java agent to be loaded to do the measurement. + * + * Inspired by Dr. Heinz Kabutz's Java Specialist Newsletter Issue #142 + * + * @author Chris Dennis + * @author Alex Snaps + * + * @link http://www.javaspecialists.eu/archive/Issue142.html + */ +public class AgentSizer implements ObjectSizer { + + static { + ByteBuddyAgent.install(); + } + + private final Instrumentation instrumentation; + + /** + * Create a new agent-based object sizer. + * + * @throws UnsupportedOperationException If the agent couldn't be loaded + */ + public AgentSizer() throws UnsupportedOperationException { + instrumentation = getInstrumentation(); + } + + @Override + public long sizeOf(Object obj) { + final long measuredSize = instrumentation.getObjectSize(obj); + return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), + measuredSize + CURRENT_JVM_INFORMATION.getAgentSizeOfAdjustment()); + } + + private static Instrumentation getInstrumentation() { + try { + return ByteBuddyAgent.getInstrumentation(); + } catch (IllegalStateException e) { + throw new UnsupportedOperationException(e); + } + } +} diff --git a/src/main/java/org/ehcache/sizeof/impl/JvmInformation.java b/src/main/java/org/ehcache/sizeof/impl/JvmInformation.java index 09c1df7..47d43f7 100644 --- a/src/main/java/org/ehcache/sizeof/impl/JvmInformation.java +++ b/src/main/java/org/ehcache/sizeof/impl/JvmInformation.java @@ -25,6 +25,8 @@ import javax.management.ObjectName; import javax.management.openmbean.CompositeData; +import static java.lang.Boolean.parseBoolean; + /** * Detects and represents JVM-specific properties that relate to the memory * data model for java objects that are useful for size of calculations. @@ -85,6 +87,11 @@ public boolean supportsUnsafeSizeOf() { public boolean supportsReflectionSizeOf() { return true; } + + @Override + public boolean usesNewObjectLayout() { + return false; + } }, /** @@ -281,6 +288,54 @@ public String getJvmDescription() { } }, + /** + * Represents OpenJDK 32-bit + */ + OPENJDK_32_BIT_NEW_LAYOUT(OPENJDK_32_BIT) { + + @Override + public boolean usesNewObjectLayout() { + return true; + } + + @Override + public String getJvmDescription() { + return parent.getJvmDescription() + " (New Object Layout)"; + } + }, + + /** + * Represents 64-Bit OpenJDK JVM + */ + OPENJDK_64_BIT_NEW_LAYOUT(OPENJDK_64_BIT) { + + @Override + public boolean usesNewObjectLayout() { + return true; + } + + @Override + public String getJvmDescription() { + return parent.getJvmDescription() + " (New Object Layout)"; + } + }, + + /** + * Represents 64-Bit OpenJDK JVM with Compressed OOPs + */ + OPENJDK_64_BIT_WITH_COMPRESSED_OOPS_NEW_LAYOUT(OPENJDK_64_BIT_WITH_COMPRESSED_OOPS) { + + @Override + public boolean usesNewObjectLayout() { + return true; + } + + @Override + public String getJvmDescription() { + return parent.getJvmDescription() + " (New Object Layout)"; + } + }, + /** * Represents IBM 32-bit */ @@ -356,7 +411,7 @@ public String getJvmDescription() { LOGGER.info("Detected JVM data model settings of: " + CURRENT_JVM_INFORMATION.getJvmDescription()); } - private JvmInformation parent; + protected JvmInformation parent; JvmInformation(JvmInformation parent) { this.parent = parent; @@ -411,6 +466,13 @@ public int getAgentSizeOfAdjustment() { return parent.getAgentSizeOfAdjustment(); } + /** + * The size of the jvm-specific agent result adjustment in bytes. + */ + public boolean usesNewObjectLayout() { + return parent.usesNewObjectLayout(); + } + /** * Whether the jvm can support AgentSizeOf implementation. */ @@ -493,7 +555,11 @@ private static JvmInformation detectOpenJDK() { if (isOpenJDK()) { if (is64Bit()) { - if (isHotspotCompressedOops() && isHotspotConcurrentMarkSweepGC()) { + if (isHotspotNewObjectLayout() && isHotspotCompressedOops()) { + jif = OPENJDK_64_BIT_WITH_COMPRESSED_OOPS_NEW_LAYOUT; + } else if (isHotspotNewObjectLayout()) { + jif = OPENJDK_64_BIT_NEW_LAYOUT; + } else if (isHotspotCompressedOops() && isHotspotConcurrentMarkSweepGC()) { jif = OPENJDK_64_BIT_WITH_COMPRESSED_OOPS_AND_CONCURRENT_MARK_AND_SWEEP; } else if (isHotspotCompressedOops()) { jif = OPENJDK_64_BIT_WITH_COMPRESSED_OOPS; @@ -503,7 +569,9 @@ private static JvmInformation detectOpenJDK() { jif = OPENJDK_64_BIT; } } else { - if (isHotspotConcurrentMarkSweepGC()) { + if (isHotspotNewObjectLayout()) { + jif = OPENJDK_32_BIT_NEW_LAYOUT; + } else if (isHotspotConcurrentMarkSweepGC()) { jif = OPENJDK_32_BIT_WITH_CONCURRENT_MARK_AND_SWEEP; } else { jif = OPENJDK_32_BIT; @@ -613,6 +681,15 @@ private static boolean isHotspotCompressedOops() { } } + private static boolean isHotspotNewObjectLayout() { + String useNewFieldLayout = getHotSpotVmOptionValue("UseNewFieldLayout"); + if (useNewFieldLayout == null) { + return parseBoolean(getHotSpotVmOptionValue("UseEmptySlotsInSupers")); + } else { + return !parseBoolean(useNewFieldLayout) && parseBoolean(getHotSpotVmOptionValue("UseEmptySlotsInSupers")); + } + } + private static String getHotSpotVmOptionValue(String name) { try { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); diff --git a/src/main/java/org/ehcache/sizeof/impl/ReflectionSizeOf.java b/src/main/java/org/ehcache/sizeof/impl/ReflectionSizeOf.java deleted file mode 100644 index 07eaf01..0000000 --- a/src/main/java/org/ehcache/sizeof/impl/ReflectionSizeOf.java +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import org.ehcache.sizeof.SizeOf; -import org.ehcache.sizeof.filters.SizeOfFilter; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.lang.reflect.Array; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayDeque; -import java.util.Deque; - -import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; - -/** - * SizeOf that uses reflection to measure on heap size of object graphs - * Inspired by Dr. Heinz Kabutz's Java Specialist Newsletter Issue #78 - * - * @author Alex Snaps - * @author Chris Dennis - * - * @link http://www.javaspecialists.eu/archive/Issue078.html - */ -public class ReflectionSizeOf extends SizeOf { - - private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionSizeOf.class); - - /** - * Builds a new SizeOf that will not filter fields and will cache reflected fields - * - * @see #ReflectionSizeOf(SizeOfFilter, boolean, boolean) - */ - public ReflectionSizeOf() { - this(new PassThroughFilter()); - } - - /** - * Builds a new SizeOf that will filter fields and will cache reflected fields - * - * @param fieldFilter The filter to apply - * @see #ReflectionSizeOf(SizeOfFilter, boolean, boolean) - * @see SizeOfFilter - */ - public ReflectionSizeOf(SizeOfFilter fieldFilter) { - this(fieldFilter, true, true); - } - - /** - * Builds a new SizeOf that will filter fields - * - * @param fieldFilter The filter to apply - * @param caching Whether to cache reflected fields - * @param bypassFlyweight whether "Flyweight Objects" are to be ignored - * @see SizeOfFilter - */ - public ReflectionSizeOf(SizeOfFilter fieldFilter, boolean caching, boolean bypassFlyweight) { - super(fieldFilter, caching, bypassFlyweight); - - if (!CURRENT_JVM_INFORMATION.supportsReflectionSizeOf()) { - LOGGER.warn("ReflectionSizeOf is not always accurate on the JVM (" + CURRENT_JVM_INFORMATION.getJvmDescription() + - "). Please consider enabling AgentSizeOf."); - } - } - - /** - * {@inheritDoc} - */ - @Override - public long sizeOf(Object obj) { - if (obj == null) { - return 0; - } - - Class aClass = obj.getClass(); - if (aClass.isArray()) { - return guessArraySize(obj); - } else { - long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize(); - - Deque> classStack = new ArrayDeque<>(); - for (Class klazz = aClass; klazz != null; klazz = klazz.getSuperclass()) { - classStack.push(klazz); - } - - while (!classStack.isEmpty()) { - Class klazz = classStack.pop(); - - //assuming default class layout - int oops = 0; - int doubles = 0; - int words = 0; - int shorts = 0; - int bytes = 0; - for (Field f : klazz.getDeclaredFields()) { - if (Modifier.isStatic(f.getModifiers())) { - continue; - } - if (f.getType().isPrimitive()) { - switch (PrimitiveType.forType(f.getType())) { - case BOOLEAN: - case BYTE: - bytes++; - break; - case SHORT: - case CHAR: - shorts++; - break; - case INT: - case FLOAT: - words++; - break; - case DOUBLE: - case LONG: - doubles++; - break; - default: - throw new AssertionError(); - } - } else { - oops++; - } - } - if (doubles > 0 && (size % PrimitiveType.LONG.getSize()) != 0) { - long length = PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize()); - size += PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize()); - - while (length >= PrimitiveType.INT.getSize() && words > 0) { - length -= PrimitiveType.INT.getSize(); - words--; - } - while (length >= PrimitiveType.SHORT.getSize() && shorts > 0) { - length -= PrimitiveType.SHORT.getSize(); - shorts--; - } - while (length >= PrimitiveType.BYTE.getSize() && bytes > 0) { - length -= PrimitiveType.BYTE.getSize(); - bytes--; - } - while (length >= PrimitiveType.getReferenceSize() && oops > 0) { - length -= PrimitiveType.getReferenceSize(); - oops--; - } - } - size += PrimitiveType.DOUBLE.getSize() * doubles; - size += PrimitiveType.INT.getSize() * words; - size += PrimitiveType.SHORT.getSize() * shorts; - size += PrimitiveType.BYTE.getSize() * bytes; - - if (oops > 0) { - if ((size % PrimitiveType.getReferenceSize()) != 0) { - size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize()); - } - size += oops * PrimitiveType.getReferenceSize(); - } - - if ((doubles + words + shorts + bytes + oops) > 0 && (size % PrimitiveType.getReferenceSize()) != 0) { - size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize()); - } - } - if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { - size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); - } - return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize()); - } - } - - private long guessArraySize(Object obj) { - long size = PrimitiveType.getArraySize(); - int length = Array.getLength(obj); - if (length != 0) { - Class arrayElementClazz = obj.getClass().getComponentType(); - if (arrayElementClazz.isPrimitive()) { - size += length * PrimitiveType.forType(arrayElementClazz).getSize(); - } else { - size += length * PrimitiveType.getReferenceSize(); - } - } - if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { - size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); - } - return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize()); - } -} diff --git a/src/main/java/org/ehcache/sizeof/impl/ReflectionSizer.java b/src/main/java/org/ehcache/sizeof/impl/ReflectionSizer.java new file mode 100644 index 0000000..11460c4 --- /dev/null +++ b/src/main/java/org/ehcache/sizeof/impl/ReflectionSizer.java @@ -0,0 +1,207 @@ +/** + * Copyright Terracotta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehcache.sizeof.impl; + +import org.ehcache.sizeof.ObjectSizer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.Deque; + +import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; +import static org.ehcache.sizeof.impl.ReflectionSizer.FieldCounts.empty; + +/** + * SizeOf that uses reflection to measure on heap size of object graphs + * Inspired by Dr. Heinz Kabutz's Java Specialist Newsletter Issue #78 + * + * @author Alex Snaps + * @author Chris Dennis + * + * @link http://www.javaspecialists.eu/archive/Issue078.html + */ +public class ReflectionSizer implements ObjectSizer { + + private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionSizer.class); + + /** + * Builds a new SizeOf that will not filter fields and will cache reflected fields + */ + public ReflectionSizer() { + if (!CURRENT_JVM_INFORMATION.supportsReflectionSizeOf()) { + LOGGER.warn("ReflectionSizeOf is not always accurate on the JVM (" + CURRENT_JVM_INFORMATION.getJvmDescription() + + "). Please consider enabling AgentSizeOf."); + } + } + + /** + * {@inheritDoc} + */ + @Override + public long sizeOf(Object obj) { + if (obj == null) { + return 0; + } + + Class aClass = obj.getClass(); + if (aClass.isArray()) { + return guessArraySize(obj); + } else { + long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize(); + + Deque> classStack = new ArrayDeque<>(); + for (Class klazz = aClass; klazz != null; klazz = klazz.getSuperclass()) { + classStack.push(klazz); + } + + if (CURRENT_JVM_INFORMATION.usesNewObjectLayout()) { + size = addNewLayoutSize(size, classStack); + } else { + size = addOldLayoutSize(size, classStack); + } + + if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { + size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); + } + return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize()); + } + } + + private long addOldLayoutSize(long size, Deque> classStack) { + while (!classStack.isEmpty()) { + size = empty().add(classStack.pop()).packOnTo(size); + } + return size; + } + + private long addNewLayoutSize(long size, Deque> classStack) { + FieldCounts counts = empty(); + + while (!classStack.isEmpty()) { + counts.add(classStack.pop()); + } + + return counts.packOnTo(size); + } + + static class FieldCounts { + private int oops; + private int doubles; + private int words; + private int shorts; + private int bytes; + + static FieldCounts empty() { + return new FieldCounts(); + } + + FieldCounts add(Class klazz) { + for (Field f : klazz.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + continue; + } + if (f.getType().isPrimitive()) { + switch (PrimitiveType.forType(f.getType())) { + case BOOLEAN: + case BYTE: + bytes++; + break; + case SHORT: + case CHAR: + shorts++; + break; + case INT: + case FLOAT: + words++; + break; + case DOUBLE: + case LONG: + doubles++; + break; + default: + throw new AssertionError(); + } + } else { + oops++; + } + } + return this; + } + + long packOnTo(long size) { + if (doubles > 0 && (size % PrimitiveType.LONG.getSize()) != 0) { + long length = PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize()); + size += PrimitiveType.LONG.getSize() - (size % PrimitiveType.LONG.getSize()); + + while (length >= PrimitiveType.INT.getSize() && words > 0) { + length -= PrimitiveType.INT.getSize(); + words--; + } + while (length >= PrimitiveType.SHORT.getSize() && shorts > 0) { + length -= PrimitiveType.SHORT.getSize(); + shorts--; + } + while (length >= PrimitiveType.BYTE.getSize() && bytes > 0) { + length -= PrimitiveType.BYTE.getSize(); + bytes--; + } + while (length >= PrimitiveType.getReferenceSize() && oops > 0) { + length -= PrimitiveType.getReferenceSize(); + oops--; + } + } + size += PrimitiveType.DOUBLE.getSize() * doubles; + size += PrimitiveType.INT.getSize() * words; + size += PrimitiveType.SHORT.getSize() * shorts; + size += PrimitiveType.BYTE.getSize() * bytes; + + if (oops > 0) { + if ((size % PrimitiveType.getReferenceSize()) != 0) { + size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize()); + } + size += oops * PrimitiveType.getReferenceSize(); + } + + if ((doubles + words + shorts + bytes + oops) > 0 && (size % PrimitiveType.getReferenceSize()) != 0) { + size += PrimitiveType.getReferenceSize() - (size % PrimitiveType.getReferenceSize()); + } + + return size; + + } + } + + private long guessArraySize(Object obj) { + long size = PrimitiveType.getArraySize(); + int length = Array.getLength(obj); + if (length != 0) { + Class arrayElementClazz = obj.getClass().getComponentType(); + if (arrayElementClazz.isPrimitive()) { + size += length * PrimitiveType.forType(arrayElementClazz).getSize(); + } else { + size += length * PrimitiveType.getReferenceSize(); + } + } + if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { + size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); + } + return Math.max(size, CURRENT_JVM_INFORMATION.getMinimumObjectSize()); + } +} diff --git a/src/main/java/org/ehcache/sizeof/impl/SizeOfAgent.java b/src/main/java/org/ehcache/sizeof/impl/SizeOfAgent.java deleted file mode 100644 index ad35605..0000000 --- a/src/main/java/org/ehcache/sizeof/impl/SizeOfAgent.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import java.lang.instrument.Instrumentation; - -/** - * @author Alex Snaps - */ -public class SizeOfAgent { - - private static volatile Instrumentation instrumentation; - private static final String NO_INSTRUMENTATION_SYSTEM_PROPERTY_NAME = "org.ehcache.sizeof.agent.instrumentationSystemProperty"; - - public static void premain(String options, Instrumentation inst) { - SizeOfAgent.instrumentation = inst; - registerSystemProperty(); - } - - public static void agentmain(String options, Instrumentation inst) { - SizeOfAgent.instrumentation = inst; - registerSystemProperty(); - } - - private static void registerSystemProperty() { - if (Boolean.getBoolean(NO_INSTRUMENTATION_SYSTEM_PROPERTY_NAME)) { - System.getProperties().put(AgentLoader.INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME, instrumentation); - } - } - - public static Instrumentation getInstrumentation() { - return instrumentation; - } - - private SizeOfAgent() { - //not instantiable - } -} diff --git a/src/main/java/org/ehcache/sizeof/impl/UnsafeSizeOf.java b/src/main/java/org/ehcache/sizeof/impl/UnsafeSizer.java similarity index 52% rename from src/main/java/org/ehcache/sizeof/impl/UnsafeSizeOf.java rename to src/main/java/org/ehcache/sizeof/impl/UnsafeSizer.java index 2157435..1bea769 100644 --- a/src/main/java/org/ehcache/sizeof/impl/UnsafeSizeOf.java +++ b/src/main/java/org/ehcache/sizeof/impl/UnsafeSizer.java @@ -15,9 +15,9 @@ */ package org.ehcache.sizeof.impl; +import org.ehcache.sizeof.ObjectSizer; +import org.ehcache.sizeof.util.UnsafeAccess; import sun.misc.Unsafe; -import org.ehcache.sizeof.SizeOf; -import org.ehcache.sizeof.filters.SizeOfFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,58 +34,18 @@ * @author Chris Dennis */ @SuppressWarnings("restriction") -public class UnsafeSizeOf extends SizeOf { +public class UnsafeSizer implements ObjectSizer { + private static final Logger LOGGER = LoggerFactory.getLogger(UnsafeSizer.class); - private static final Logger LOGGER = LoggerFactory.getLogger(UnsafeSizeOf.class); - - private static final Unsafe UNSAFE; - - static { - Unsafe unsafe; - try { - Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); - unsafeField.setAccessible(true); - unsafe = (Unsafe)unsafeField.get(null); - } catch (Throwable t) { - unsafe = null; - } - UNSAFE = unsafe; - } + private static final Unsafe UNSAFE = UnsafeAccess.getUnsafe(); /** * Builds a new SizeOf that will not filter fields and will cache reflected fields * * @throws UnsupportedOperationException If Unsafe isn't accessible - * @see #UnsafeSizeOf(org.ehcache.sizeof.filters.SizeOfFilter, boolean, boolean) */ - public UnsafeSizeOf() throws UnsupportedOperationException { - this(new PassThroughFilter()); - } - - /** - * Builds a new SizeOf that will filter fields according to the provided filter and will cache reflected fields - * - * @param filter The filter to apply - * @throws UnsupportedOperationException If Unsafe isn't accessible - * @see #UnsafeSizeOf(org.ehcache.sizeof.filters.SizeOfFilter, boolean, boolean) - * @see org.ehcache.sizeof.filters.SizeOfFilter - */ - public UnsafeSizeOf(SizeOfFilter filter) throws UnsupportedOperationException { - this(filter, true, true); - } - - /** - * Builds a new SizeOf that will filter fields according to the provided filter - * - * @param filter The filter to apply - * @param caching whether to cache reflected fields - * @param bypassFlyweight whether "Flyweight Objects" are to be ignored - * @throws UnsupportedOperationException If Unsafe isn't accessible - * @see SizeOfFilter - */ - public UnsafeSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweight) throws UnsupportedOperationException { - super(filter, caching, bypassFlyweight); + public UnsafeSizer() throws UnsupportedOperationException { if (UNSAFE == null) { throw new UnsupportedOperationException("sun.misc.Unsafe instance not accessible"); } @@ -94,7 +54,6 @@ public UnsafeSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweigh LOGGER.warn("UnsafeSizeOf is not always accurate on the JVM (" + CURRENT_JVM_INFORMATION.getJvmDescription() + "). Please consider enabling AgentSizeOf."); } - } /** @@ -113,29 +72,29 @@ public long sizeOf(Object obj) { } return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size); } else { + long lastFieldOffset = -1; for (Class klazz = obj.getClass(); klazz != null; klazz = klazz.getSuperclass()) { - long lastFieldOffset = -1; for (Field f : klazz.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { lastFieldOffset = Math.max(lastFieldOffset, UNSAFE.objectFieldOffset(f)); } } - if (lastFieldOffset > 0) { - lastFieldOffset += CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment(); - lastFieldOffset += 1; - if ((lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { - lastFieldOffset += CURRENT_JVM_INFORMATION.getObjectAlignment() - - (lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment()); - } - return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), lastFieldOffset); - } } - - long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize(); - if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { - size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); + if (lastFieldOffset > 0) { + lastFieldOffset += CURRENT_JVM_INFORMATION.getFieldOffsetAdjustment(); + lastFieldOffset += 1; + if ((lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { + lastFieldOffset += CURRENT_JVM_INFORMATION.getObjectAlignment() - + (lastFieldOffset % CURRENT_JVM_INFORMATION.getObjectAlignment()); + } + return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), lastFieldOffset); + } else { + long size = CURRENT_JVM_INFORMATION.getObjectHeaderSize(); + if ((size % CURRENT_JVM_INFORMATION.getObjectAlignment()) != 0) { + size += CURRENT_JVM_INFORMATION.getObjectAlignment() - (size % CURRENT_JVM_INFORMATION.getObjectAlignment()); + } + return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size); } - return Math.max(CURRENT_JVM_INFORMATION.getMinimumObjectSize(), size); } } diff --git a/src/main/java/org/ehcache/sizeof/util/UnsafeAccess.java b/src/main/java/org/ehcache/sizeof/util/UnsafeAccess.java new file mode 100644 index 0000000..7e8c52d --- /dev/null +++ b/src/main/java/org/ehcache/sizeof/util/UnsafeAccess.java @@ -0,0 +1,43 @@ +/** + * Copyright Terracotta, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ehcache.sizeof.util; + +import sun.misc.Unsafe; + +import java.lang.reflect.Field; + +public final class UnsafeAccess { + + private static final Unsafe UNSAFE; + + static { + Unsafe unsafe; + try { + Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + unsafe = (Unsafe)unsafeField.get(null); + } catch (Throwable t) { + unsafe = null; + } + UNSAFE = unsafe; + } + + @SuppressWarnings("MS_EXPOSE_REP") + public static Unsafe getUnsafe() { + return UNSAFE; + } + +} diff --git a/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOf.java b/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOf.java index dba3fc1..ed03715 100644 --- a/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOf.java +++ b/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOf.java @@ -16,60 +16,53 @@ package org.ehcache.sizeof; import org.ehcache.sizeof.filters.SizeOfFilter; -import org.ehcache.sizeof.impl.AgentSizeOf; +import org.ehcache.sizeof.impl.AgentSizer; +import org.ehcache.sizeof.impl.JvmInformation; import org.ehcache.sizeof.impl.PassThroughFilter; -import org.ehcache.sizeof.impl.ReflectionSizeOf; -import org.ehcache.sizeof.impl.UnsafeSizeOf; +import org.ehcache.sizeof.impl.ReflectionSizer; +import org.ehcache.sizeof.impl.UnsafeSizer; import java.lang.reflect.Array; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Stream; -import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; +import static java.util.stream.Collectors.toList; /** * @author Alex Snaps */ public class CrossCheckingSizeOf extends SizeOf { - private final List engines; + private final List engines; + + public static Stream objectSizers() { + if (JvmInformation.CURRENT_JVM_INFORMATION.supportsReflectionSizeOf()) { + return Stream.of(new AgentSizer(), new UnsafeSizer(), new ReflectionSizer()); + } else { + return Stream.of(new AgentSizer(), new UnsafeSizer()); + } + } public CrossCheckingSizeOf() { this(new PassThroughFilter()); } + public CrossCheckingSizeOf(Stream sizers) { + this(new PassThroughFilter(), true, true, sizers); + } + public CrossCheckingSizeOf(boolean bypassFlyweight) { - this(new PassThroughFilter(), true, bypassFlyweight); + this(new PassThroughFilter(), true, bypassFlyweight, objectSizers()); } public CrossCheckingSizeOf(SizeOfFilter filter) { - this(filter, true, true); + this(filter, true, true, objectSizers()); } - public CrossCheckingSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweight) { - super(filter, caching, bypassFlyweight); - engines = new ArrayList<>(); - - try { - engines.add(new AgentSizeOf()); - } catch (UnsupportedOperationException usoe) { - System.err.println("Not using AgentSizeOf: " + usoe); - } - try { - engines.add(new UnsafeSizeOf()); - } catch (UnsupportedOperationException usoe) { - System.err.println("Not using UnsafeSizeOf: " + usoe); - } - if (CURRENT_JVM_INFORMATION.supportsReflectionSizeOf()) { - try { - engines.add(new ReflectionSizeOf()); - } catch (UnsupportedOperationException usoe) { - System.err.println("Not using ReflectionSizeOf: " + usoe); - } - } else { - System.err.println(CURRENT_JVM_INFORMATION.getJvmDescription() + " detected: not using ReflectionSizeOf"); - } + private CrossCheckingSizeOf(SizeOfFilter filter, boolean caching, boolean bypassFlyweight, Stream sizerFactories) { + super(filter, caching, bypassFlyweight); + this.engines = sizerFactories.collect(toList()); if (engines.isEmpty()) { throw new AssertionError("No SizeOf engines available"); } diff --git a/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOfIT.java b/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOfIT.java index 6fb3747..198f9eb 100644 --- a/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOfIT.java +++ b/src/test/java/org/ehcache/sizeof/CrossCheckingSizeOfIT.java @@ -24,18 +24,9 @@ import java.util.List; import java.util.Set; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; import org.junit.Test; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; - -import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; -import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; -import static org.objectweb.asm.Opcodes.ACC_PUBLIC; -import static org.objectweb.asm.Opcodes.ALOAD; -import static org.objectweb.asm.Opcodes.INVOKESPECIAL; -import static org.objectweb.asm.Opcodes.RETURN; -import static org.objectweb.asm.Opcodes.V1_6; /** * @@ -94,43 +85,21 @@ private static Set> permutations(Collection choices, Comparator generateClassHierarchy(List>> classes) { - TestClassLoader loader = new TestClassLoader(); - - String superClassDesc = "java/lang/Object"; + ByteBuddy byteBuddy = new ByteBuddy(); + + Class type = Object.class; int classIndex = 0; - + for (List> fields : classes) { - String classDesc = "A" + classIndex++; - ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES); - cw.visit(V1_6, ACC_PUBLIC, classDesc, null, superClassDesc, null); - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, superClassDesc, "", "()V", false); - mv.visitInsn(RETURN); - mv.visitMaxs(1, 1); - mv.visitEnd(); + DynamicType.Builder builder = byteBuddy.subclass(type).name("A" + classIndex++); int fieldIndex = 0; for (Class field : fields) { - cw.visitField(ACC_PUBLIC, "a" + fieldIndex++, Type.getDescriptor(field), null, null); + builder = builder.defineField("a" + fieldIndex++, field); } - cw.visitEnd(); - loader.defineClass(classDesc, cw.toByteArray()); - superClassDesc = classDesc; + type = builder.make().load(type.getClassLoader()).getLoaded(); } - try { - return loader.loadClass("A" + (classIndex - 1)); - } catch (ClassNotFoundException | NoClassDefFoundError e) { - throw new AssertionError(e); - } - } - - private static class TestClassLoader extends ClassLoader { - - public void defineClass(String name, byte[] classbytes) { - defineClass(name, classbytes, 0, classbytes.length); - } + return type; } } diff --git a/src/test/java/org/ehcache/sizeof/SizeOfTest.java b/src/test/java/org/ehcache/sizeof/SizeOfTest.java index 565e36c..9f4b3ab 100644 --- a/src/test/java/org/ehcache/sizeof/SizeOfTest.java +++ b/src/test/java/org/ehcache/sizeof/SizeOfTest.java @@ -15,12 +15,16 @@ */ package org.ehcache.sizeof; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.ClassFileVersion; import org.ehcache.sizeof.impl.JvmInformation; +import org.ehcache.sizeof.impl.UnsafeSizer; import org.junit.Assert; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; +import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; @@ -39,6 +43,8 @@ import javax.xml.datatype.DatatypeConstants; +import static net.bytebuddy.ClassFileVersion.JAVA_V14; +import static org.ehcache.sizeof.CrossCheckingSizeOf.objectSizers; import static org.ehcache.sizeof.impl.JvmInformation.CURRENT_JVM_INFORMATION; import static org.ehcache.sizeof.impl.JvmInformation.UNKNOWN_32_BIT; import static org.ehcache.sizeof.impl.JvmInformation.UNKNOWN_64_BIT; @@ -78,7 +84,7 @@ public void testCreatesSizeOfEngine() { } @Test - public void testSizeOfFlyweight() throws Exception { + public void testSizeOfFlyweight() { SizeOf sizeOf = new CrossCheckingSizeOf(false); Assert.assertThat(deepSizeOf(sizeOf, 42), is(not(0L))); } @@ -120,6 +126,10 @@ public void testSizeOf() throws Exception { assertThat(deepSizeOf(sizeOf, new Pair(new Object(), new Object())), "deepSizeOf(new Pair(new Object(), new Object()))"); assertThat(deepSizeOf(sizeOf, new ReentrantReadWriteLock()), "deepSizeOf(new ReentrantReadWriteLock())"); + if (ClassFileVersion.ofThisVm().isAtLeast(JAVA_V14)) { + assertThat(new CrossCheckingSizeOf(objectSizers().filter(s -> !(s instanceof UnsafeSizer))).sizeOf(javaRecord()), "sizeOf(javaRecord())"); + } + if (!sizeOfFailures.isEmpty()) { StringBuilder sb = new StringBuilder(); for (AssertionError e : sizeOfFailures) { @@ -260,4 +270,13 @@ private EvilPair(final Object one, final Object two) { this.copyTwo = two; } } + + private Object javaRecord() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + return new ByteBuddy().makeRecord() + .defineRecordComponent("foo", Integer.TYPE) + .defineRecordComponent("bar", Long.TYPE) + .make().load(getClass().getClassLoader()) + .getLoaded().getDeclaredConstructor(Integer.TYPE, Long.TYPE).newInstance(42, 42L); + } + } diff --git a/src/test/java/org/ehcache/sizeof/SizeOfTestValues.java b/src/test/java/org/ehcache/sizeof/SizeOfTestValues.java index 819beae..3bbfdec 100755 --- a/src/test/java/org/ehcache/sizeof/SizeOfTestValues.java +++ b/src/test/java/org/ehcache/sizeof/SizeOfTestValues.java @@ -140,6 +140,60 @@ public class SizeOfTestValues { CORRECT_SIZES.put(JvmInformation.HOTSPOT_64_BIT_WITH_COMPRESSED_OOPS_AND_CONCURRENT_MARK_AND_SWEEP, hotspot64BitWithCompressedOopsAndCMS); CORRECT_SIZES.put(JvmInformation.OPENJDK_64_BIT_WITH_COMPRESSED_OOPS_AND_CONCURRENT_MARK_AND_SWEEP, hotspot64BitWithCompressedOopsAndCMS); + Map openjdk32BitNewLayout = new HashMap<>(); + openjdk32BitNewLayout.put("sizeOf(new Object())", 8L); + openjdk32BitNewLayout.put("sizeOf(new Integer(1))", 16L); + openjdk32BitNewLayout.put("sizeOf(1000)", 16L); + openjdk32BitNewLayout.put("deepSizeOf(new SomeClass(false))", 16L); + openjdk32BitNewLayout.put("deepSizeOf(new SomeClass(true))", 24L); + openjdk32BitNewLayout.put("sizeOf(new Object[] { })", 16L); + openjdk32BitNewLayout.put("sizeOf(new Object[] { new Object(), new Object(), new Object(), new Object() })", 32L); + openjdk32BitNewLayout.put("sizeOf(new int[] { })", 16L); + openjdk32BitNewLayout.put("sizeOf(new int[] { 987654, 876543, 765432, 654321 })", 32L); + openjdk32BitNewLayout.put("deepSizeOf(new Pair(null, null))", 16L); + openjdk32BitNewLayout.put("deepSizeOf(new Pair(new Object(), null))", 24L); + openjdk32BitNewLayout.put("deepSizeOf(new Pair(new Object(), new Object()))", 32L); + openjdk32BitNewLayout.put("deepSizeOf(new ReentrantReadWriteLock())", 112L); + openjdk32BitNewLayout.put("sizeOf(javaRecord())", 24L); + + CORRECT_SIZES.put(JvmInformation.OPENJDK_32_BIT_NEW_LAYOUT, openjdk32BitNewLayout); + + Map openjdk64BitNewLayout = new HashMap<>(); + openjdk64BitNewLayout.put("sizeOf(new Object())", 16L); + openjdk64BitNewLayout.put("sizeOf(new Integer(1))", 24L); + openjdk64BitNewLayout.put("sizeOf(1000)", 24L); + openjdk64BitNewLayout.put("deepSizeOf(new SomeClass(false))", 24L); + openjdk64BitNewLayout.put("deepSizeOf(new SomeClass(true))", 40L); + openjdk64BitNewLayout.put("sizeOf(new Object[] { })", 24L); + openjdk64BitNewLayout.put("sizeOf(new Object[] { new Object(), new Object(), new Object(), new Object() })", 56L); + openjdk64BitNewLayout.put("sizeOf(new int[] { })", 24L); + openjdk64BitNewLayout.put("sizeOf(new int[] { 987654, 876543, 765432, 654321 })", 40L); + openjdk64BitNewLayout.put("deepSizeOf(new Pair(null, null))", 32L); + openjdk64BitNewLayout.put("deepSizeOf(new Pair(new Object(), null))", 48L); + openjdk64BitNewLayout.put("deepSizeOf(new Pair(new Object(), new Object()))", 64L); + openjdk64BitNewLayout.put("deepSizeOf(new ReentrantReadWriteLock())", 192L); + openjdk64BitNewLayout.put("sizeOf(javaRecord())", 24L); + + CORRECT_SIZES.put(JvmInformation.OPENJDK_64_BIT_NEW_LAYOUT, openjdk64BitNewLayout); + + Map openjdk64BitWithCompressedOopsNewLayout = new HashMap<>(); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new Object())", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new Integer(1))", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(1000)", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new SomeClass(false))", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new SomeClass(true))", 32L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new Object[] { })", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new Object[] { new Object(), new Object(), new Object(), new Object() })", 32L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new int[] { })", 16L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(new int[] { 987654, 876543, 765432, 654321 })", 32L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new Pair(null, null))", 24L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new Pair(new Object(), null))", 40L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new Pair(new Object(), new Object()))", 56L); + openjdk64BitWithCompressedOopsNewLayout.put("deepSizeOf(new ReentrantReadWriteLock())", 120L); + openjdk64BitWithCompressedOopsNewLayout.put("sizeOf(javaRecord())", 24L); + + CORRECT_SIZES.put(JvmInformation.OPENJDK_64_BIT_WITH_COMPRESSED_OOPS_NEW_LAYOUT, openjdk64BitWithCompressedOopsNewLayout); + Map ibm32Bit = new HashMap<>(); ibm32Bit.put("sizeOf(new Object())", 16L); ibm32Bit.put("sizeOf(new Integer(1))", 16L); diff --git a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderRaceTest.java b/src/test/java/org/ehcache/sizeof/impl/AgentLoaderRaceTest.java deleted file mode 100755 index 93cd248..0000000 --- a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderRaceTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import org.junit.Assert; -import org.junit.Assume; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import static org.hamcrest.core.AnyOf.anyOf; -import static org.hamcrest.core.IsNot.not; -import static org.hamcrest.core.IsNull.nullValue; -import static org.hamcrest.core.StringStartsWith.startsWith; - -/** - * @author Alex Snaps - */ -public class AgentLoaderRaceTest { - - /* - * This test tries to expose an agent loading race seen in MNK-3255. - * - * To trigger a failure the locking in AgentLoader.loadAgent has to be removed - * and a sleep can be added after the check but before the load to open the - * race wider. - */ - @Test - public void testAgentLoaderRace() throws InterruptedException, ExecutionException { - final URL[] urls = ((URLClassLoader)AgentSizeOf.class.getClassLoader()).getURLs(); - - Callable agentLoader1 = new Loader(new URLClassLoader(urls, null)); - Callable agentLoader2 = new Loader(new URLClassLoader(urls, null)); - - ExecutorService executor = Executors.newFixedThreadPool(2); - List> results = executor.invokeAll(Arrays.asList(agentLoader1, agentLoader2)); - - - for (Future f : results) { - Assert.assertThat(f.get(), nullValue()); - } - } - - static class Loader implements Callable { - - private final ClassLoader loader; - - Loader(ClassLoader loader) { - this.loader = loader; - } - - public Throwable call() { - try { - loader.loadClass(AgentSizeOf.class.getName()).newInstance(); - return null; - } catch (Throwable t) { - return t; - } - } - - } -} diff --git a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderSystemPropTest.java b/src/test/java/org/ehcache/sizeof/impl/AgentLoaderSystemPropTest.java deleted file mode 100644 index 12c0025..0000000 --- a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderSystemPropTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import org.junit.After; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; - -/** - * @author Alex Snaps - */ -public class AgentLoaderSystemPropTest { - - @After - public void after() { - System.getProperties().remove("org.ehcache.sizeof.agent.instrumentationSystemProperty"); - System.getProperties().remove(AgentLoader.INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME); - } - - @Test - public void testLoadsAgentIntoSystemPropsWhenRequired() { - System.setProperty("org.ehcache.sizeof.agent.instrumentationSystemProperty", "true"); - AgentLoader.loadAgent(); - assertThat(AgentLoader.agentIsAvailable(), is(true)); - assertThat(System.getProperties().get(AgentLoader.INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME), notNullValue()); - } -} diff --git a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderTest.java b/src/test/java/org/ehcache/sizeof/impl/AgentLoaderTest.java deleted file mode 100644 index 6faf24c..0000000 --- a/src/test/java/org/ehcache/sizeof/impl/AgentLoaderTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright Terracotta, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.ehcache.sizeof.impl; - -import org.junit.After; -import org.junit.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.junit.Assert.assertThat; - -/** - * @author Alex Snaps - */ -public class AgentLoaderTest { - - @After - public void after() { - System.getProperties().remove("org.ehcache.sizeof.agent.instrumentationSystemProperty"); - System.getProperties().remove(AgentLoader.INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME); - } - - @Test - public void testLoadsAgentProperly() { - assertThat(Boolean.getBoolean("org.ehcache.sizeof.agent.instrumentationSystemProperty"), is(false)); - AgentLoader.loadAgent(); - assertThat(AgentLoader.agentIsAvailable(), is(true)); - assertThat(System.getProperties().get(AgentLoader.INSTRUMENTATION_INSTANCE_SYSTEM_PROPERTY_NAME), nullValue()); - } -}