From fca2c320777261c93008c71697eec09c0aaef4b1 Mon Sep 17 00:00:00 2001 From: Chris Dennis Date: Wed, 20 Apr 2022 14:11:50 -0400 Subject: [PATCH] Improve support for Java 9+ --- pom.xml | 89 +++---- src/hidden/resources/META-INF/MANIFEST.MF | 3 - .../java/org/ehcache/sizeof/ObjectSizer.java | 21 ++ src/main/java/org/ehcache/sizeof/SizeOf.java | 79 ++++-- .../org/ehcache/sizeof/impl/AgentLoader.java | 234 ------------------ .../org/ehcache/sizeof/impl/AgentSizeOf.java | 88 ------- .../org/ehcache/sizeof/impl/AgentSizer.java | 66 +++++ .../ehcache/sizeof/impl/JvmInformation.java | 83 ++++++- .../ehcache/sizeof/impl/ReflectionSizeOf.java | 199 --------------- .../ehcache/sizeof/impl/ReflectionSizer.java | 207 ++++++++++++++++ .../org/ehcache/sizeof/impl/SizeOfAgent.java | 51 ---- .../{UnsafeSizeOf.java => UnsafeSizer.java} | 68 ++--- .../ehcache/sizeof/CrossCheckingSizeOf.java | 55 ++-- .../ehcache/sizeof/CrossCheckingSizeOfIT.java | 51 +--- .../java/org/ehcache/sizeof/SizeOfTest.java | 19 ++ .../org/ehcache/sizeof/SizeOfTestValues.java | 54 ++++ .../sizeof/impl/AgentLoaderRaceTest.java | 84 ------- .../impl/AgentLoaderSystemPropTest.java | 43 ---- .../ehcache/sizeof/impl/AgentLoaderTest.java | 43 ---- 19 files changed, 602 insertions(+), 935 deletions(-) delete mode 100644 src/hidden/resources/META-INF/MANIFEST.MF create mode 100644 src/main/java/org/ehcache/sizeof/ObjectSizer.java delete mode 100644 src/main/java/org/ehcache/sizeof/impl/AgentLoader.java delete mode 100644 src/main/java/org/ehcache/sizeof/impl/AgentSizeOf.java create mode 100644 src/main/java/org/ehcache/sizeof/impl/AgentSizer.java delete mode 100644 src/main/java/org/ehcache/sizeof/impl/ReflectionSizeOf.java create mode 100644 src/main/java/org/ehcache/sizeof/impl/ReflectionSizer.java delete mode 100644 src/main/java/org/ehcache/sizeof/impl/SizeOfAgent.java rename src/main/java/org/ehcache/sizeof/impl/{UnsafeSizeOf.java => UnsafeSizer.java} (58%) delete mode 100755 src/test/java/org/ehcache/sizeof/impl/AgentLoaderRaceTest.java delete mode 100644 src/test/java/org/ehcache/sizeof/impl/AgentLoaderSystemPropTest.java delete mode 100644 src/test/java/org/ehcache/sizeof/impl/AgentLoaderTest.java diff --git a/pom.xml b/pom.xml index add131d..a6f8394 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.6.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/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/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 58% rename from src/main/java/org/ehcache/sizeof/impl/UnsafeSizeOf.java rename to src/main/java/org/ehcache/sizeof/impl/UnsafeSizer.java index 2157435..02642c8 100644 --- a/src/main/java/org/ehcache/sizeof/impl/UnsafeSizeOf.java +++ b/src/main/java/org/ehcache/sizeof/impl/UnsafeSizer.java @@ -15,9 +15,8 @@ */ package org.ehcache.sizeof.impl; +import org.ehcache.sizeof.ObjectSizer; import sun.misc.Unsafe; -import org.ehcache.sizeof.SizeOf; -import org.ehcache.sizeof.filters.SizeOfFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,10 +33,9 @@ * @author Chris Dennis */ @SuppressWarnings("restriction") -public class UnsafeSizeOf extends SizeOf { +public class UnsafeSizer implements ObjectSizer { - - private static final Logger LOGGER = LoggerFactory.getLogger(UnsafeSizeOf.class); + private static final Logger LOGGER = LoggerFactory.getLogger(UnsafeSizer.class); private static final Unsafe UNSAFE; @@ -57,35 +55,8 @@ public class UnsafeSizeOf extends SizeOf { * 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 +65,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 +83,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/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..1e7a75d 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; @@ -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()); - } -}