Skip to content

Commit

Permalink
Add a new IMSTRUMENTATION_AND_SPEC strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
blerer committed Jul 13, 2023
1 parent 9349d21 commit 86ea290
Show file tree
Hide file tree
Showing 20 changed files with 505 additions and 147 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/org/github/jamm/Measurable.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions src/org/github/jamm/MeasurementStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object> stack = new ArrayDeque<Object>();

Expand All @@ -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)) {
Expand All @@ -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);
Expand Down
121 changes: 103 additions & 18 deletions src/org/github/jamm/MemoryMeter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
}
}

/**
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Guess> 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<Guess> guesses = DEFAULT_GUESSES;
private boolean ignoreOuterClassReference;
private boolean ignoreKnownSingletons = true;
private boolean ignoreNonStrongReferences = true;
Expand All @@ -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<Guess> 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;
}

Expand Down
15 changes: 13 additions & 2 deletions src/org/github/jamm/MemoryMeterStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion src/org/github/jamm/strategies/ContendedUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
41 changes: 41 additions & 0 deletions src/org/github/jamm/strategies/InstrumentationAndSpecStrategy.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
final class InstrumentationStrategy implements MemoryMeterStrategy {

public final Instrumentation instrumentation;
private final Instrumentation instrumentation;

public InstrumentationStrategy(Instrumentation instrumentation) {
this.instrumentation = instrumentation;
Expand Down
7 changes: 4 additions & 3 deletions src/org/github/jamm/strategies/MemoryLayoutBasedStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 86ea290

Please sign in to comment.