From 86ea29091fa5fcb10214bcd09bd5d349a7374d27 Mon Sep 17 00:00:00 2001 From: Benjamin Lerer Date: Wed, 21 Jun 2023 17:05:59 +0200 Subject: [PATCH] Add a new IMSTRUMENTATION_AND_SPEC strategy --- README.md | 13 +- src/org/github/jamm/Measurable.java | 4 +- src/org/github/jamm/MeasurementStack.java | 5 +- src/org/github/jamm/MemoryMeter.java | 121 ++++++++-- src/org/github/jamm/MemoryMeterStrategy.java | 15 +- .../jamm/strategies/ContendedUtils.java | 2 +- .../InstrumentationAndSpecStrategy.java | 41 ++++ .../strategies/InstrumentationStrategy.java | 2 +- .../strategies/MemoryLayoutBasedStrategy.java | 7 +- .../strategies/MemoryMeterStrategies.java | 68 ++++-- .../strategies/PreJava15SpecStrategy.java | 2 +- .../strategies/PreJava15UnsafeStrategy.java | 4 +- .../github/jamm/strategies/SpecStrategy.java | 2 +- .../jamm/strategies/UnsafeStrategy.java | 4 +- test/org/github/jamm/GuessTest.java | 5 +- test/org/github/jamm/MemoryMeterTest.java | 2 +- .../jamm/jmh/BenchmarkMeasureArray.java | 72 ++++++ ...ies.java => BenchmarkMeasureInstance.java} | 73 +++---- .../jmh/BenchmarkObjectGraphTraversal.java | 206 ++++++++++++++---- .../strategies/MemoryMeterStrategyTest.java | 4 +- 20 files changed, 505 insertions(+), 147 deletions(-) create mode 100644 src/org/github/jamm/strategies/InstrumentationAndSpecStrategy.java create mode 100644 test/org/github/jamm/jmh/BenchmarkMeasureArray.java rename test/org/github/jamm/jmh/{BenchmarkStategies.java => BenchmarkMeasureInstance.java} (51%) diff --git a/README.md b/README.md index b96b8fa..7191e11 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ The 0.4.0 version comes with speed improvements and support java versions up to changes at the API level. * The `MemoryMeter` constructor and the static methods used to configure the different options (`omitSharedBufferOverhead`, `withGuessing`, `ignoreOuterClassReference`, `ignoreKnownSingletons`, `ignoreNonStrongReferences`, `enableDebug`) have been removed. Instead `MemoryMeter` instances must be created through a `Builder`. * The ability to provide a tracker for visited object has been removed. -* `Guess.NEVER` has been renamed `Guess.ALWAYS_INSTRUMENTATION` for more clarity. +* `Guess` values have been changed to each represent a single strategy. Fallback strategies can be defined through the `MemoryMeter.Builder::withGuessing` method. * `MemoryMeter.countChildren` has been removed. * The `MemoryMeter.measure` and `MemoryMeter.measureDeep` now accept `null` parameters * The behavior of the `omitSharedBufferOverhead` has been changed as it was incorrect. It was not counting correctly for direct ByteBuffers and considering some buffer has shared even if they were not. @@ -128,6 +128,13 @@ If the JVM has been started with `-javaagent`, `MemoryMeter` will use `java.lang.instrument.Instrumentation.getObjectSize` to get an estimate of the space required to store the given object. It is the safest strategy. +### Instrumentation and specification + +This strategy requires `java.lang.instrument.Instrumentation` as the `Instrumentation` strategy and will use it +to measure non array object. For measuring arrays it will use the `Specification` strategy way. +This strategy tries to combine the best of both strategies the accuracy and speed of `Instrumentation` for non array object +and the speed of SPEC for measuring array objects for which all strategy are accurate. For some reason `Instrumentation` is slower for arrays. + ### Unsafe `MemoryMeter` will use `Unsafe.objectFieldOffset` to guess the object offset. @@ -156,9 +163,9 @@ illegal accesses the JVM might emit some warning for access that only will be il ### Optimized crawling approach -For your own classes, `MemoryMeter` provide a way to avoid the use of reflections by having the class implementing the `Measurable` +For your own classes, `MemoryMeter` provides a way to avoid the use of reflections by having the class implement the `Measurable` interface. When `MemoryMeter` encounter a class that implements the `Measurable` interface it will call the `addChildrenTo` to let -the class adds its fields to the stack of objects than need to be measured instead of using reflection. Therefore avoiding the reflection cost. +the class adds its fields to the stack of objects that need to be measured instead of using reflection. Therefore avoiding the reflection cost. ### Filtering diff --git a/src/org/github/jamm/Measurable.java b/src/org/github/jamm/Measurable.java index 1c14946..2666979 100644 --- a/src/org/github/jamm/Measurable.java +++ b/src/org/github/jamm/Measurable.java @@ -1,8 +1,8 @@ package org.github.jamm; /** - * Interface that allow users to avoid crawling via reflection by adding children manually to the stack therefore - * speeding up the computation. The downside of it approach is that it push the responsibility to the user to ensure + * Interface that allows users to avoid crawling via reflection by adding children manually to the stack, therefore + * speeding up the computation. The downside of this approach is that it pushes the responsibility to the user to ensure * that all the children are provided. */ public interface Measurable { diff --git a/src/org/github/jamm/MeasurementStack.java b/src/org/github/jamm/MeasurementStack.java index 428b3fe..f7e4c63 100644 --- a/src/org/github/jamm/MeasurementStack.java +++ b/src/org/github/jamm/MeasurementStack.java @@ -24,7 +24,7 @@ public final class MeasurementStack { private final FieldAndClassFilter classFilter; /** - * Stack of object that need to be measured. + * Stack of objects that need to be measured. */ private final Deque stack = new ArrayDeque(); @@ -38,7 +38,7 @@ public final class MeasurementStack { * * @param parent the parent object * @param name the field name - * @param child the + * @param child the child to be added */ public void pushObject(Object parent, String name, Object child) { if (tracker.add(child)) { @@ -64,7 +64,6 @@ void pushRoot(Object object) { * @param index the element index */ void pushArrayElement(Object[] array, int index) { - Object child = array[index]; if (child != null && !classFilter.ignore(child.getClass()) && tracker.add(child)) { stack.push(child); diff --git a/src/org/github/jamm/MemoryMeter.java b/src/org/github/jamm/MemoryMeter.java index bc298f0..36cb6c0 100644 --- a/src/org/github/jamm/MemoryMeter.java +++ b/src/org/github/jamm/MemoryMeter.java @@ -3,9 +3,14 @@ import java.lang.instrument.Instrumentation; import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; import org.github.jamm.strategies.MemoryMeterStrategies; +import static java.util.Collections.singletonList; +import static java.util.Collections.unmodifiableList; + public final class MemoryMeter { public static void premain(String options, Instrumentation inst) { @@ -24,20 +29,82 @@ public static boolean hasUnsafe() { return MemoryMeterStrategies.getInstance().hasUnsafe(); } + /** + * The different strategies that can be used by a {@code MemoryMeter} instance to measure the shallow size of an object. + */ public enum Guess { - /* If instrumentation is not available, error when measuring */ - ALWAYS_INSTRUMENTATION, - /* If instrumentation is available, use it, otherwise guess the size using predefined specifications */ - FALLBACK_SPEC, - /* If instrumentation is available, use it, otherwise guess the size using sun.misc.Unsafe */ - FALLBACK_UNSAFE, - /* If instrumentation is available, use it, otherwise guess the size using sun.misc.Unsafe; if that is unavailable, - * guess using predefined specifications.*/ - FALLBACK_BEST, - /* Always guess the size of measured objects using predefined specifications*/ - ALWAYS_SPEC, - /* Always guess the size of measured objects using sun.misc.Unsafe */ - ALWAYS_UNSAFE; + /** + * Relies on {@code java.lang.instrument.Instrumentation} to measure shallow object size. +<<<<<<< 0bcfac1a30229a269d9a5b1f54f6c68100a5b067 + * It requires {@code Instrumentation} to be available, but it is the most accurate strategy. +======= + * It requires {@code Instrumentation} to be available but is the most accurate strategy. +>>>>>>> Add a new IMSTRUMENTATION_AND_SPEC strategy + */ + INSTRUMENTATION { + + public boolean requireInstrumentation() { + return true; + } + }, + /** + * Relies on {@code java.lang.instrument.Instrumentation} to measure non array object and the SPEC approach to measure arrays. + * This strategy tries to combine the best of both strategies the accuracy and speed of {@code Instrumentation} for non array object + * and the speed of SPEC for measuring array objects for which all strategy are accurate. For some reason {@code Instrumentation} is slower for arrays. + */ + INSTRUMENTATION_AND_SPEC { + + public boolean requireInstrumentation() { + return true; + } + }, + /** + * Relies on {@code Unsafe} to measure shallow object size. + * It requires {@code Unsafe} to be available. After INSTRUMENTATION based strategy UNSAFE is the most accurate strategy. + */ + UNSAFE { + + public boolean requireUnsafe() { + return true; + } + + public boolean canBeUsedAsFallbackFrom(Guess strategy) { + return strategy.requireInstrumentation(); + } + }, + /** + * Computes the shallow size of objects using VM information. + */ + SPEC { + + public boolean requireUnsafe() { + return true; + } + + public boolean canBeUsedAsFallbackFrom(Guess guess) { + return true; + } + }; + + /** + * Checks if this strategy requires {@code Instrumentation} to be present. + * @return {@code true} if this strategy requires {@code Instrumentation} to be present, {@code false} otherwise. + */ + public boolean requireInstrumentation() { + return false; + } + + /** + * Checks if this strategy requires {@code Unsafe} to be present. + * @return {@code true} if this strategy requires {@code Unsafe} to be present, {@code false} otherwise. + */ + public boolean requireUnsafe() { + return false; + } + + public boolean canBeUsedAsFallbackFrom(Guess guess) { + return false; + } } /** @@ -72,7 +139,7 @@ public enum Guess { private MemoryMeter(Builder builder) { - this(MemoryMeterStrategies.getInstance().getStrategy(builder.guess), + this(MemoryMeterStrategies.getInstance().getStrategy(builder.guesses), Filters.getClassFilters(builder.ignoreKnownSingletons), Filters.getFieldFilters(builder.ignoreKnownSingletons, builder.ignoreOuterClassReference, builder.ignoreNonStrongReferences), builder.omitSharedBufferOverhead, @@ -433,7 +500,15 @@ private void addArrayChildren(Object[] current, MeasurementStack stack) { */ public static final class Builder { - private Guess guess = Guess.ALWAYS_INSTRUMENTATION; + /** + * The default strategy + */ + private static final List DEFAULT_GUESSES = unmodifiableList(singletonList(Guess.INSTRUMENTATION)); + + /** + * The strategy to perform shallow measurements and its fallback strategies in case the required classes are not available. + */ + private List guesses = DEFAULT_GUESSES; private boolean ignoreOuterClassReference; private boolean ignoreKnownSingletons = true; private boolean ignoreNonStrongReferences = true; @@ -449,10 +524,20 @@ public MemoryMeter build() { } /** - * See {@link Guess} for possible guess-modes. + * Specify what should be the strategy used to measure the shallow size of object. */ - public Builder withGuessing(Guess guess) { - this.guess = guess; + public Builder withGuessing(Guess measurementStrategy, Guess... fallbacks) { + + List guessList = new ArrayList<>(fallbacks.length + 1); + guessList.add(measurementStrategy); + Guess previous = measurementStrategy; + for (Guess fallback : fallbacks) { + if (!fallback.canBeUsedAsFallbackFrom(previous)) { + throw new IllegalArgumentException("The " + fallback + " strategy cannot be used as fallback for the " + previous + " strategy."); + } + guessList.add(fallback); + } + this.guesses = guessList; return this; } diff --git a/src/org/github/jamm/MemoryMeterStrategy.java b/src/org/github/jamm/MemoryMeterStrategy.java index b2ca6ba..010a158 100644 --- a/src/org/github/jamm/MemoryMeterStrategy.java +++ b/src/org/github/jamm/MemoryMeterStrategy.java @@ -4,8 +4,8 @@ * Represents a strategy to measure the shallow memory used by a Java object. */ @FunctionalInterface -public interface MemoryMeterStrategy -{ +public interface MemoryMeterStrategy { + /** * Measures the shallow memory used by the specified object. * @@ -14,6 +14,17 @@ public interface MemoryMeterStrategy */ long measure(Object object); + /** + * Measures the shallow memory used by the specified array. + * + * @param array the array to measure + * @param type the array type + * @return the shallow memory usage of the specified array + */ + default long measureArray(Object array, Class type) { + return measure(array); + } + /** * Measures the shallow memory used by the specified array. * diff --git a/src/org/github/jamm/strategies/ContendedUtils.java b/src/org/github/jamm/strategies/ContendedUtils.java index b894d3b..522c151 100644 --- a/src/org/github/jamm/strategies/ContendedUtils.java +++ b/src/org/github/jamm/strategies/ContendedUtils.java @@ -62,7 +62,7 @@ public static boolean isClassAnnotatedWithContended(Class type) { * @param f the field to check * @return {@code true} if the specified field is annotated with {@code Contended}, {@code false} otherwise. */ - public static final boolean isFieldAnnotatedWithContended(Field f) { + public static boolean isFieldAnnotatedWithContended(Field f) { return f.isAnnotationPresent(CONTENDED_CLASS); } diff --git a/src/org/github/jamm/strategies/InstrumentationAndSpecStrategy.java b/src/org/github/jamm/strategies/InstrumentationAndSpecStrategy.java new file mode 100644 index 0000000..17d0973 --- /dev/null +++ b/src/org/github/jamm/strategies/InstrumentationAndSpecStrategy.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you 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.github.jamm.strategies; + +import java.lang.instrument.Instrumentation; + +import org.github.jamm.MemoryLayoutSpecification; + +/** + * Strategy that use {@code java.lang.instrument.Instrumentation} to measure non array object and the SPEC approach to measure arrays. + * This strategy tries to combine the best of both strategies the accuracy and speed of {@code Instrumentation} for non array object + * and the speed of SPEC for measuring array objects for which all strategy are accurate. For some reason {@code Instrumentation} is slower for arrays. + */ +public class InstrumentationAndSpecStrategy extends MemoryLayoutBasedStrategy { + + private final Instrumentation instrumentation; + + public InstrumentationAndSpecStrategy(MemoryLayoutSpecification memoryLayout, Instrumentation instrumentation) { + super(memoryLayout, memoryLayout.getArrayHeaderSize()); + this.instrumentation = instrumentation; + } + + @Override + public long measureInstance(Object object, Class type) { + return instrumentation.getObjectSize(object); + } +} diff --git a/src/org/github/jamm/strategies/InstrumentationStrategy.java b/src/org/github/jamm/strategies/InstrumentationStrategy.java index 7992805..311eec0 100644 --- a/src/org/github/jamm/strategies/InstrumentationStrategy.java +++ b/src/org/github/jamm/strategies/InstrumentationStrategy.java @@ -10,7 +10,7 @@ */ final class InstrumentationStrategy implements MemoryMeterStrategy { - public final Instrumentation instrumentation; + private final Instrumentation instrumentation; public InstrumentationStrategy(Instrumentation instrumentation) { this.instrumentation = instrumentation; diff --git a/src/org/github/jamm/strategies/MemoryLayoutBasedStrategy.java b/src/org/github/jamm/strategies/MemoryLayoutBasedStrategy.java index 3049043..bf54aea 100644 --- a/src/org/github/jamm/strategies/MemoryLayoutBasedStrategy.java +++ b/src/org/github/jamm/strategies/MemoryLayoutBasedStrategy.java @@ -33,7 +33,7 @@ public MemoryLayoutBasedStrategy(MemoryLayoutSpecification memoryLayout, @Override public final long measure(Object object) { Class type = object.getClass(); - return type.isArray() ? measureArray(object, type) : measureInstance(type); + return type.isArray() ? measureArray(object, type) : measureInstance(object, type); } @Override @@ -89,10 +89,11 @@ public long measureString(String s) { /** * Measures the shallow memory used by objects of the specified class. * + * @param instance the object to measure * @param type the object type * @return the shallow memory used by the object */ - protected abstract long measureInstance(Class type); + protected abstract long measureInstance(Object instance, Class type); /** * Measure the shallow memory used by the specified array. @@ -101,7 +102,7 @@ public long measureString(String s) { * @param type the array type * @return the shallow memory used by the specified array */ - private final long measureArray(Object instance, Class type) { + public final long measureArray(Object instance, Class type) { int length = Array.getLength(instance); int elementSize = measureField(type.getComponentType()); return computeArraySize(length, elementSize); diff --git a/src/org/github/jamm/strategies/MemoryMeterStrategies.java b/src/org/github/jamm/strategies/MemoryMeterStrategies.java index ba922af..323b31e 100644 --- a/src/org/github/jamm/strategies/MemoryMeterStrategies.java +++ b/src/org/github/jamm/strategies/MemoryMeterStrategies.java @@ -2,7 +2,10 @@ import java.lang.instrument.Instrumentation; import java.lang.invoke.MethodHandle; +import java.util.LinkedList; +import java.util.List; import java.util.Optional; +import java.util.Queue; import org.github.jamm.MemoryLayoutSpecification; import org.github.jamm.MemoryMeter.Guess; @@ -35,6 +38,12 @@ public final class MemoryMeterStrategies */ private final MemoryMeterStrategy instrumentationStrategy; + /** + * Strategy relying on instrumentation to measure non array object and the SPEC approach to measure arrays. + * {@code null} if the instrumentation was not provided + */ + private final MemoryMeterStrategy instrumentationAndSpecStrategy; + /** * Strategy relying on unsafe or {@code null} if unsafe could not be loaded */ @@ -47,11 +56,13 @@ public final class MemoryMeterStrategies private MemoryMeterStrategies(MemoryLayoutSpecification memoryLayoutSpecification, MemoryMeterStrategy instrumentationStrategy, + MemoryMeterStrategy instrumentationAndSpecStrategy, MemoryMeterStrategy unsafeStrategy, MemoryMeterStrategy specStrategy) { this.memoryLayoutSpecification = memoryLayoutSpecification; this.instrumentationStrategy = instrumentationStrategy; + this.instrumentationAndSpecStrategy = instrumentationAndSpecStrategy; this.unsafeStrategy = unsafeStrategy; this.specStrategy = specStrategy; } @@ -75,6 +86,7 @@ private static MemoryMeterStrategies createStrategies() { Optional mayBeIsHiddenMH = mayBeIsHiddenMethodHandle(); MemoryMeterStrategy instrumentationStrategy = createInstrumentationStrategy(); + MemoryMeterStrategy instrumentationAndSpecStrategy = createInstrumentationAndSpecStrategy(specification); MemoryMeterStrategy specStrategy = createSpecStrategy(specification, mayBeIsHiddenMH); MemoryMeterStrategy unsafeStrategy = createUnsafeStrategy(specification, mayBeIsHiddenMH, (MemoryLayoutBasedStrategy) specStrategy); @@ -85,7 +97,7 @@ private static MemoryMeterStrategies createStrategies() { + ", unsafe=" + (unsafeStrategy != null) + ", " + specification); - return new MemoryMeterStrategies(specification, instrumentationStrategy, unsafeStrategy, specStrategy); + return new MemoryMeterStrategies(specification, instrumentationStrategy, instrumentationAndSpecStrategy, unsafeStrategy, specStrategy); } private static MemoryMeterStrategy createSpecStrategy(MemoryLayoutSpecification specification, @@ -143,6 +155,10 @@ private static MemoryMeterStrategy createInstrumentationStrategy() { return instrumentation != null ? new InstrumentationStrategy(instrumentation) : null; } + private static MemoryMeterStrategy createInstrumentationAndSpecStrategy(MemoryLayoutSpecification specification) { + return instrumentation != null ? new InstrumentationAndSpecStrategy(specification, instrumentation) : null; + } + public boolean hasInstrumentation() { return instrumentationStrategy != null; } @@ -159,31 +175,35 @@ public MemoryLayoutSpecification getMemoryLayoutSpecification() { return this.memoryLayoutSpecification; } - @SuppressWarnings("incomplete-switch") - public MemoryMeterStrategy getStrategy(Guess guess) { - switch (guess) { - case ALWAYS_UNSAFE: - if (!hasUnsafe()) + public MemoryMeterStrategy getStrategy(List guessList) { + + Queue guesses = new LinkedList<>(guessList); + + while (true) { + + Guess guess = guesses.poll(); + + if (guess.requireInstrumentation()) { + + if (hasInstrumentation()) + return guess == Guess.INSTRUMENTATION_AND_SPEC ? instrumentationAndSpecStrategy + : instrumentationStrategy; + + if (guesses.isEmpty()) + throw new IllegalStateException("Instrumentation is not set; Jamm must be set as -javaagent"); + + } else if (guess.requireUnsafe()) { + + if (hasUnsafe()) + return unsafeStrategy; + + if (guesses.isEmpty()) throw new IllegalStateException("sun.misc.Unsafe could not be obtained. The SecurityManager must permit access to sun.misc.Unsafe"); - return unsafeStrategy; - case ALWAYS_SPEC: + + } else { + return specStrategy; - default: - if (!hasInstrumentation()) { - switch (guess) { - case ALWAYS_INSTRUMENTATION: - throw new IllegalStateException("Instrumentation is not set; Jamm must be set as -javaagent"); - case FALLBACK_UNSAFE: - if (!hasUnsafe()) - throw new IllegalStateException("Instrumentation is not set and sun.misc.Unsafe could not be obtained; Jamm must be set as -javaagent, or the SecurityManager must permit access to sun.misc.Unsafe"); - case FALLBACK_BEST: - if (hasUnsafe()) - return unsafeStrategy; - case FALLBACK_SPEC: - return specStrategy; - } - } - return instrumentationStrategy; + } } } } diff --git a/src/org/github/jamm/strategies/PreJava15SpecStrategy.java b/src/org/github/jamm/strategies/PreJava15SpecStrategy.java index fac4891..0c36552 100644 --- a/src/org/github/jamm/strategies/PreJava15SpecStrategy.java +++ b/src/org/github/jamm/strategies/PreJava15SpecStrategy.java @@ -121,7 +121,7 @@ protected long alignFieldBlock(long sizeOfDeclaredFields) { } @Override - public final long measureInstance(Class type) { + public final long measureInstance(Object instance, Class type) { long size = sizeOfFields(type, false); return roundTo(size, memoryLayout.getObjectAlignment()); diff --git a/src/org/github/jamm/strategies/PreJava15UnsafeStrategy.java b/src/org/github/jamm/strategies/PreJava15UnsafeStrategy.java index f8ede12..d7fe017 100644 --- a/src/org/github/jamm/strategies/PreJava15UnsafeStrategy.java +++ b/src/org/github/jamm/strategies/PreJava15UnsafeStrategy.java @@ -48,14 +48,14 @@ public PreJava15UnsafeStrategy(MemoryLayoutSpecification memoryLayout, } @Override - public long measureInstance(Class type) { + public long measureInstance(Object instance, Class type) { try { // If the class is a record 'unsafe.objectFieldOffset(f)' will throw an UnsupportedOperationException // In those cases, rather than failing, we rely on the Spec strategy to provide the measurement. if (mayBeIsRecordMH.isPresent() && ((Boolean) mayBeIsRecordMH.get().invoke(type))) - return recordsStrategy.measureInstance(type); + return recordsStrategy.measureInstance(instance, type); int annotatedClassesWithoutFields = 0; // Keep track of the @Contended annotated classes without fields while (type != null) { diff --git a/src/org/github/jamm/strategies/SpecStrategy.java b/src/org/github/jamm/strategies/SpecStrategy.java index 4169181..65647f5 100644 --- a/src/org/github/jamm/strategies/SpecStrategy.java +++ b/src/org/github/jamm/strategies/SpecStrategy.java @@ -24,7 +24,7 @@ public SpecStrategy(MemoryLayoutSpecification memoryLayout) } @Override - public final long measureInstance(Class type) { + public final long measureInstance(Object instance, Class type) { long size = memoryLayout.getObjectHeaderSize() + sizeOfDeclaredFields(type); while ((type = type.getSuperclass()) != Object.class && type != null) diff --git a/src/org/github/jamm/strategies/UnsafeStrategy.java b/src/org/github/jamm/strategies/UnsafeStrategy.java index a3ff96e..4eae196 100644 --- a/src/org/github/jamm/strategies/UnsafeStrategy.java +++ b/src/org/github/jamm/strategies/UnsafeStrategy.java @@ -62,14 +62,14 @@ public UnsafeStrategy(MemoryLayoutSpecification memoryLayout, } @Override - public long measureInstance(Class type) { + public long measureInstance(Object instance, Class type) { try { // If the class is a hidden class ore a record 'unsafe.objectFieldOffset(f)' will throw an UnsupportedOperationException // In those cases, rather than failing, we rely on the Spec strategy to provide the measurement. if ((Boolean) isRecordMH.invoke(type) || (Boolean) isHiddenMH.invoke(type)) - return hiddenClassesOrRecordsStrategy.measureInstance(type); + return hiddenClassesOrRecordsStrategy.measureInstance(instance, type); long size = 0; boolean isLastFieldWithinContentionGroup = false; diff --git a/test/org/github/jamm/GuessTest.java b/test/org/github/jamm/GuessTest.java index 5aaf2a0..924212b 100644 --- a/test/org/github/jamm/GuessTest.java +++ b/test/org/github/jamm/GuessTest.java @@ -43,8 +43,9 @@ public static Collection guesses() { List guesses = new ArrayList<>(); - guesses.add(MemoryMeter.Guess.ALWAYS_UNSAFE); - guesses.add(MemoryMeter.Guess.ALWAYS_SPEC); + guesses.add(MemoryMeter.Guess.INSTRUMENTATION_AND_SPEC); + guesses.add(MemoryMeter.Guess.UNSAFE); + guesses.add(MemoryMeter.Guess.SPEC); return guesses; } diff --git a/test/org/github/jamm/MemoryMeterTest.java b/test/org/github/jamm/MemoryMeterTest.java index efb0b50..7582c4d 100644 --- a/test/org/github/jamm/MemoryMeterTest.java +++ b/test/org/github/jamm/MemoryMeterTest.java @@ -26,7 +26,7 @@ public class MemoryMeterTest @Parameterized.Parameters public static Collection guesses() { - return Arrays.asList(MemoryMeter.Guess.ALWAYS_INSTRUMENTATION, MemoryMeter.Guess.ALWAYS_UNSAFE, MemoryMeter.Guess.ALWAYS_SPEC); + return Arrays.asList(MemoryMeter.Guess.INSTRUMENTATION, MemoryMeter.Guess.UNSAFE, MemoryMeter.Guess.SPEC); } private final MemoryMeter.Guess guess; diff --git a/test/org/github/jamm/jmh/BenchmarkMeasureArray.java b/test/org/github/jamm/jmh/BenchmarkMeasureArray.java new file mode 100644 index 0000000..64807c9 --- /dev/null +++ b/test/org/github/jamm/jmh/BenchmarkMeasureArray.java @@ -0,0 +1,72 @@ +package org.github.jamm.jmh; + +import java.lang.reflect.Array; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import org.github.jamm.MemoryMeter; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@Threads(3) +@Fork(value = 1, jvmArgsPrepend = { + "-javaagent:target/jamm-0.4.0-SNAPSHOT.jar" +}) +@Warmup(iterations=4, time=5, timeUnit=TimeUnit.SECONDS) +@Measurement(iterations=5, time=5, timeUnit=TimeUnit.SECONDS) +@BenchmarkMode(Mode.Throughput) +@State(Scope.Thread) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class BenchmarkMeasureArray +{ + @Param({"INSTRUMENTATION", "INSTRUMENTATION_AND_SPEC", "SPEC", "UNSAFE"}) + private String guess; + + private MemoryMeter meter; + + private static Object[] arrays; + + static + { + try { + + Class[] choices = new Class[] {byte.class, int.class, double.class, Object.class}; + + Random random = new Random(); + + arrays = new Object[1000]; + for (int i = 0; i < arrays.length; i++) { + arrays[i] = Array.newInstance(choices[random.nextInt(choices.length)], random.nextInt(100)); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Setup(Level.Iteration) + public void setup() throws Exception + { + MemoryMeter.Guess guess = MemoryMeter.Guess.valueOf(this.guess); + this.meter = MemoryMeter.builder().withGuessing(guess).build(); + } + + @Benchmark + public void measure(Blackhole bh) + { + for (Object o : arrays) + bh.consume(meter.measure(o)); + } +} diff --git a/test/org/github/jamm/jmh/BenchmarkStategies.java b/test/org/github/jamm/jmh/BenchmarkMeasureInstance.java similarity index 51% rename from test/org/github/jamm/jmh/BenchmarkStategies.java rename to test/org/github/jamm/jmh/BenchmarkMeasureInstance.java index 8e9728d..2baf260 100644 --- a/test/org/github/jamm/jmh/BenchmarkStategies.java +++ b/test/org/github/jamm/jmh/BenchmarkMeasureInstance.java @@ -8,6 +8,7 @@ import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Param; @@ -15,73 +16,73 @@ import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; @Threads(3) @Fork(value = 1, jvmArgsPrepend = { - "-javaagent:target/jamm-0.4.0-SNAPSHOT.jar", - "-Xms16g", "-Xmx16g", - "-XX:+UseG1GC", - "-XX:+AlwaysPreTouch" + "-javaagent:target/jamm-0.4.0-SNAPSHOT.jar" }) -@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations=4, time=5, timeUnit=TimeUnit.SECONDS) +@Measurement(iterations=5, time=5, timeUnit=TimeUnit.SECONDS) +@BenchmarkMode(Mode.Throughput) @State(Scope.Thread) @OutputTimeUnit(TimeUnit.MILLISECONDS) -public class BenchmarkStategies +public class BenchmarkMeasureInstance { - @Param({"ALWAYS_INSTRUMENTATION", "ALWAYS_SPEC", "ALWAYS_UNSAFE"}) + @Param({"INSTRUMENTATION", "INSTRUMENTATION_AND_SPEC", "SPEC", "UNSAFE"}) private String guess; private MemoryMeter meter; - private Object[] objects; + private static Object[] objects; - @Setup(Level.Iteration) - public void setup() throws Exception + static { - Class[] choices = new Class[] {ClassWithoutFields.class, - ClassWithOnePrimitiveFields.class, - ClassWithOneObjectField.class, - ClassWithTreeObjectFields.class, - ClassWithOneObjectFieldAndTwoPrimitives.class, - ClassWithFiveObjectFields.class}; - - Random random = new Random(); - - objects = new Object[1000]; - for (int i = 0; i < objects.length; i++) - { - objects[i] = choices[random.nextInt(choices.length)].newInstance(); + try { + Class[] choices = new Class[] {ClassWithoutFields.class, + ClassWithOnePrimitiveFields.class, + ClassWithOneObjectField.class, + ClassWithTreeObjectFields.class, + ClassWithOneObjectFieldAndTwoPrimitives.class, + ClassWithFiveObjectFields.class}; + + Random random = new Random(); + + objects = new Object[1000]; + for (int i = 0; i < objects.length; i++) { + objects[i] = choices[random.nextInt(choices.length)].newInstance(); + } + } catch (Exception e) { + e.printStackTrace(); } + } + @Setup(Level.Iteration) + public void setup() throws Exception { MemoryMeter.Guess guess = MemoryMeter.Guess.valueOf(this.guess); this.meter = MemoryMeter.builder().withGuessing(guess).build(); } @Benchmark - public void measure(Blackhole bh) - { + public void measure(Blackhole bh) { for (Object o : objects) bh.consume(meter.measure(o)); } - public static class ClassWithoutFields - { + public static class ClassWithoutFields { } - public static class ClassWithOnePrimitiveFields - { + public static class ClassWithOnePrimitiveFields { int intField; } - public static class ClassWithOneObjectField - { + public static class ClassWithOneObjectField { Object field = new Object(); } - public static class ClassWithTreeObjectFields - { + public static class ClassWithTreeObjectFields { Object first = new Object(); Object second = new Object(); @@ -89,8 +90,7 @@ public static class ClassWithTreeObjectFields String[] third = new String[] {"one", "two"}; } - public static class ClassWithOneObjectFieldAndTwoPrimitives - { + public static class ClassWithOneObjectFieldAndTwoPrimitives { byte first; Object second = new Object(); @@ -98,8 +98,7 @@ public static class ClassWithOneObjectFieldAndTwoPrimitives double third; } - public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields - { + public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields { Object fourth = new Object(); int[] fifth = new int[12]; diff --git a/test/org/github/jamm/jmh/BenchmarkObjectGraphTraversal.java b/test/org/github/jamm/jmh/BenchmarkObjectGraphTraversal.java index 8a6a563..d5ec12c 100644 --- a/test/org/github/jamm/jmh/BenchmarkObjectGraphTraversal.java +++ b/test/org/github/jamm/jmh/BenchmarkObjectGraphTraversal.java @@ -5,6 +5,8 @@ import org.github.jamm.FieldAndClassFilter; import org.github.jamm.FieldFilter; import org.github.jamm.Filters; +import org.github.jamm.Measurable; +import org.github.jamm.MeasurementStack; import org.github.jamm.MemoryMeter; import org.github.jamm.MemoryMeterStrategy; import org.github.jamm.NoopMemoryMeterListener; @@ -12,95 +14,152 @@ import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.Threads; +import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.infra.Blackhole; @Threads(3) @Fork(value = 1, jvmArgsPrepend = { - "-javaagent:target/jamm-0.4.0-SNAPSHOT.jar", - "-Xms16g", "-Xmx16g", - "-XX:+UseG1GC", - "-XX:+AlwaysPreTouch" + "-javaagent:target/jamm-0.4.0-SNAPSHOT.jar" }) -@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations=4, time=5, timeUnit=TimeUnit.SECONDS) +@Measurement(iterations=5, time=5, timeUnit=TimeUnit.SECONDS) +@BenchmarkMode(Mode.Throughput) @State(Scope.Thread) -@OutputTimeUnit(TimeUnit.NANOSECONDS) +@OutputTimeUnit(TimeUnit.MILLISECONDS) public class BenchmarkObjectGraphTraversal { private MemoryMeter meter; /** - * The object being measured + * The object being measured through reflection */ - private Object obj; + private static Object OBJ; + + /** + * The object being measured through the Measurable interface + */ + private static Object MEASURABLE; + + static { + + OBJ = new ClassWithFiveObjectFields(new byte[20], + new ClassWithOneObjectFieldsAndTwoPrimitives((byte) 8, new ClassWithoutFields(), Double.MAX_VALUE), + new ClassWithOneObjectField(new Object[] {"test", new ClassWithOneObjectField(new Object()), new int[3]}), + new ClassWithTreeObjectFields(new ClassWithTreeObjectFields(Boolean.TRUE, new ClassWithOnePrimitiveFields(12), new ClassWithoutFields()), + new ClassWithOneObjectField(new Object()), + new ClassWithoutFields()), + "end"); + + MEASURABLE = new MeasurableClassWithFiveObjectFields(new byte[20], + new MeasurableClassWithOneObjectFieldsAndTwoPrimitives((byte) 8, new MeasurableClassWithoutFields(), Double.MAX_VALUE), + new MeasurableClassWithOneObjectField(new Object[] {"test", new MeasurableClassWithOneObjectField(new Object()), new int[3]}), + new MeasurableClassWithTreeObjectFields(new MeasurableClassWithTreeObjectFields(Boolean.TRUE, new MeasurableClassWithOnePrimitiveFields(12), new MeasurableClassWithoutFields()), + new MeasurableClassWithOneObjectField(new Object()), + new MeasurableClassWithoutFields()), + "end"); + } @Setup(Level.Iteration) - public void setup() throws Exception - { + public void setup() throws Exception { MemoryMeterStrategy strategy = o -> 1; FieldFilter fieldFilter = Filters.getFieldFilters(false, false, false); FieldAndClassFilter classFilter = Filters.getClassFilters(false); + this.meter = new MemoryMeter(strategy, classFilter, fieldFilter, false, NoopMemoryMeterListener.FACTORY); + } - this.obj = new ClassWithFiveObjectFields(new byte[20], - new ClassWithOneObjectFieldsAndTwoPrimitives((byte) 8, new ClassWithoutFields(), Double.MAX_VALUE), - new ClassWithOneObjectField(new Object[] {"test", new ClassWithOneObjectField(new Object()), new int[3]}), - new ClassWithTreeObjectFields(new ClassWithTreeObjectFields(Boolean.TRUE, new ClassWithOnePrimitiveFields(12), new ClassWithoutFields()), - new ClassWithOneObjectField(new Object()), - new ClassWithoutFields()), - "end"); + @Benchmark + public void measureThroughReflection(Blackhole bh) { + bh.consume(meter.measureDeep(OBJ)); } @Benchmark - public void measure(Blackhole bh) - { - bh.consume(meter.measureDeep(obj)); + public void measureThroughMeasurable(Blackhole bh) { + bh.consume(meter.measureDeep(MEASURABLE)); + } + + public static class ClassWithoutFields { + + } + + public static class MeasurableClassWithoutFields implements Measurable { + + @Override + public void addChildrenTo(MeasurementStack stack) + { + } } - public static class ClassWithoutFields - { + @SuppressWarnings("unused") + public static class ClassWithOnePrimitiveFields { + + private int intField; + + public ClassWithOnePrimitiveFields(int intField) { + this.intField = intField; + } } @SuppressWarnings("unused") - public static class ClassWithOnePrimitiveFields - { + public static class MeasurableClassWithOnePrimitiveFields implements Measurable { + private int intField; - public ClassWithOnePrimitiveFields(int intField) - { + public MeasurableClassWithOnePrimitiveFields(int intField) { this.intField = intField; } + + @Override + public void addChildrenTo(MeasurementStack stack) { + } } @SuppressWarnings("unused") - public static class ClassWithOneObjectField - { + public static class ClassWithOneObjectField { + + public static String staticField = "static"; + + private Object field; + + public ClassWithOneObjectField(Object field) { + this.field = field; + } + } + + @SuppressWarnings("unused") + public static class MeasurableClassWithOneObjectField implements Measurable { + public static Object staticField = "static"; private Object field; - public ClassWithOneObjectField(Object field) - { + public MeasurableClassWithOneObjectField(Object field) { this.field = field; } + + @Override + public void addChildrenTo(MeasurementStack stack) { + stack.pushObject(this, "field", field); + } } @SuppressWarnings("unused") - public static class ClassWithTreeObjectFields - { + public static class ClassWithTreeObjectFields { + private Object first; private Object second; private Object third; - public ClassWithTreeObjectFields(Object first, Object second, Object third) - { + public ClassWithTreeObjectFields(Object first, Object second, Object third) { this.first = first; this.second = second; this.third = third; @@ -108,34 +167,97 @@ public ClassWithTreeObjectFields(Object first, Object second, Object third) } @SuppressWarnings("unused") - public static class ClassWithOneObjectFieldsAndTwoPrimitives - { + public static class MeasurableClassWithTreeObjectFields implements Measurable { + + private Object first; + + private Object second; + + private Object third; + + public MeasurableClassWithTreeObjectFields(Object first, Object second, Object third) { + this.first = first; + this.second = second; + this.third = third; + } + + @Override + public void addChildrenTo(MeasurementStack stack) { + stack.pushObject(this, "first", first); + stack.pushObject(this, "second", second); + stack.pushObject(this, "third", third); + } + } + + @SuppressWarnings("unused") + public static class ClassWithOneObjectFieldsAndTwoPrimitives { + private byte first; private Object second; private double third; - public ClassWithOneObjectFieldsAndTwoPrimitives(byte first, Object second, double third) - { + public ClassWithOneObjectFieldsAndTwoPrimitives(byte first, Object second, double third) { + this.first = first; + this.second = second; + this.third = third; + } + } + + @SuppressWarnings("unused") + public static class MeasurableClassWithOneObjectFieldsAndTwoPrimitives implements Measurable { + + private byte first; + + private Object second; + + private double third; + + public MeasurableClassWithOneObjectFieldsAndTwoPrimitives(byte first, Object second, double third) { this.first = first; this.second = second; this.third = third; } + + @Override + public void addChildrenTo(MeasurementStack stack) { + stack.pushObject(this, "second", second); + } } @SuppressWarnings("unused") - public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields - { + public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields { + private Object fourth; private Object fifth; - public ClassWithFiveObjectFields(Object first, Object second, Object third, Object fourth, Object fifth) - { + public ClassWithFiveObjectFields(Object first, Object second, Object third, Object fourth, Object fifth) { super(first, second, third); this.fourth = fourth; this.fifth = fifth; } } + + @SuppressWarnings("unused") + public static class MeasurableClassWithFiveObjectFields extends MeasurableClassWithTreeObjectFields { + + private Object fourth; + + private Object fifth; + + public MeasurableClassWithFiveObjectFields(Object first, Object second, Object third, Object fourth, Object fifth) { + super(first, second, third); + this.fourth = fourth; + this.fifth = fifth; + } + + @Override + public void addChildrenTo(MeasurementStack stack) { + super.addChildrenTo(stack); + stack.pushObject(this, "fourth", fourth); + stack.pushObject(this, "fifth", fifth); + } + } } diff --git a/test/org/github/jamm/strategies/MemoryMeterStrategyTest.java b/test/org/github/jamm/strategies/MemoryMeterStrategyTest.java index 8beba97..619a1be 100644 --- a/test/org/github/jamm/strategies/MemoryMeterStrategyTest.java +++ b/test/org/github/jamm/strategies/MemoryMeterStrategyTest.java @@ -39,7 +39,7 @@ public class MemoryMeterStrategyTest @Parameterized.Parameters public static Collection guesses() { - return Arrays.asList(MemoryMeter.Guess.ALWAYS_UNSAFE, MemoryMeter.Guess.ALWAYS_SPEC); + return Arrays.asList(MemoryMeter.Guess.INSTRUMENTATION_AND_SPEC, MemoryMeter.Guess.UNSAFE, MemoryMeter.Guess.SPEC); } public MemoryMeterStrategyTest(MemoryMeter.Guess guess) { @@ -48,7 +48,7 @@ public MemoryMeterStrategyTest(MemoryMeter.Guess guess) { @Before public void setUp() { - reference = MemoryMeter.builder().withGuessing(Guess.ALWAYS_INSTRUMENTATION).build(); + reference = MemoryMeter.builder().withGuessing(Guess.INSTRUMENTATION).build(); tested = MemoryMeter.builder().withGuessing(guess).build(); }