Jamm provides MemoryMeter
, a Java agent for all Java versions to
measure actual object memory use including JVM overhead.
To use MemoryMeter
, start the JVM with -javaagent:<path to>/jamm.jar
.
You can then use MemoryMeter in your code like this:
MemoryMeter meter = MemoryMeter.builder().build();
meter.measure(object);
meter.measureDeep(object);
See MemoryMeter.Builder
for more
options.
If you would like to use MemoryMeter
in a web application, make sure
that you do NOT put this jar in WEB-INF/lib
, as that may cause problems
since your code is accessing a MemoryMeter from a different class loader
than the one loaded by the -javaagent
and won't see it as initialized.
If you want MemoryMeter
not to measure some specific classes, you can
mark the classes (or interfaces) using the
@Unmetered
annotation.
It is good to reuse existing MemoryMeter
instances. Creating new MemoryMeter
instances can and will cause significant performance penalties and also
unnecessary side effects, since all guess-modes rely on java.lang.ClassValue
.
Gradle:
dependencies {
implementation("com.github.jbellis:jamm:0.4.0")
}
Maven:
<dependency>
<groupId>com.github.jbellis</groupId>
<artifactId>jamm</artifactId>
<version>0.4.0</version>
</dependency>
Build + test:
./gradlew jar test
To run the microbenchmark:
./gradlew microbench
build/microbench <JMH options...>
Public to local Maven repo (in case you need that)
./gradlew publishToMavenLocal
You can just open the project in IntelliJ. If you've opened the Maven-ish branch(es) in IntelliJ before,
you'll have to remove the .idea
folder.
See Microbench.java
To run the microbenchmarks, execute ./gradlew microbench
and run build/microbench
from the command line.
Some microbenchmark results
(JVM options: -Xms16g -Xmx16g -XX:+UseG1GC -XX:+AlwaysPreTouch
):
Benchmark (guess) (nested) (refs) Mode Cnt Score Error Units
Microbench.arrayByteArray ALWAYS_SPEC 100 4 avgt 3 0.012 ± 0.019 us/op
Microbench.arrayByteArray ALWAYS_UNSAFE 100 4 avgt 3 0.012 ± 0.019 us/op
Microbench.arrayByteArray ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.012 ± 0.017 us/op
Microbench.cls1 ALWAYS_SPEC 100 4 avgt 3 0.026 ± 0.004 us/op
Microbench.cls1 ALWAYS_UNSAFE 100 4 avgt 3 0.026 ± 0.005 us/op
Microbench.cls1 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.026 ± 0.004 us/op
Microbench.cls2 ALWAYS_SPEC 100 4 avgt 3 0.350 ± 0.155 us/op
Microbench.cls2 ALWAYS_UNSAFE 100 4 avgt 3 0.387 ± 0.030 us/op
Microbench.cls2 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.355 ± 0.040 us/op
Microbench.cls3 ALWAYS_SPEC 100 4 avgt 3 0.761 ± 0.087 us/op
Microbench.cls3 ALWAYS_UNSAFE 100 4 avgt 3 0.733 ± 0.130 us/op
Microbench.cls3 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.738 ± 0.099 us/op
Microbench.deeplyNested ALWAYS_SPEC 100 4 avgt 3 49.825 ± 1.482 us/op
Microbench.deeplyNested ALWAYS_UNSAFE 100 4 avgt 3 46.260 ± 4.466 us/op
Microbench.deeplyNested ALWAYS_INSTRUMENTATION 100 4 avgt 3 52.301 ± 8.285 us/op
Microbench.justByteArray ALWAYS_SPEC 100 4 avgt 3 0.014 ± 0.005 us/op
Microbench.justByteArray ALWAYS_UNSAFE 100 4 avgt 3 0.014 ± 0.004 us/op
Microbench.justByteArray ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.014 ± 0.006 us/op
Microbench.justByteBuffer ALWAYS_SPEC 100 4 avgt 3 0.056 ± 0.011 us/op
Microbench.justByteBuffer ALWAYS_UNSAFE 100 4 avgt 3 0.058 ± 0.018 us/op
Microbench.justByteBuffer ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.058 ± 0.029 us/op
Microbench.justString ALWAYS_SPEC 100 4 avgt 3 0.059 ± 0.061 us/op
Microbench.justString ALWAYS_UNSAFE 100 4 avgt 3 0.057 ± 0.019 us/op
Microbench.justString ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.056 ± 0.001 us/op
Benchmark (guess) (nested) (refs) Mode Cnt Score Error Units
Microbench.arrayByteArray ALWAYS_SPEC 100 4 avgt 3 0.011 ± 0.007 us/op
Microbench.arrayByteArray ALWAYS_UNSAFE 100 4 avgt 3 0.011 ± 0.006 us/op
Microbench.arrayByteArray ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.011 ± 0.007 us/op
Microbench.cls1 ALWAYS_SPEC 100 4 avgt 3 0.023 ± 0.009 us/op
Microbench.cls1 ALWAYS_UNSAFE 100 4 avgt 3 0.025 ± 0.039 us/op
Microbench.cls1 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.024 ± 0.012 us/op
Microbench.cls2 ALWAYS_SPEC 100 4 avgt 3 0.357 ± 0.034 us/op
Microbench.cls2 ALWAYS_UNSAFE 100 4 avgt 3 0.365 ± 0.008 us/op
Microbench.cls2 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.366 ± 0.100 us/op
Microbench.cls3 ALWAYS_SPEC 100 4 avgt 3 0.773 ± 0.178 us/op
Microbench.cls3 ALWAYS_UNSAFE 100 4 avgt 3 0.754 ± 0.328 us/op
Microbench.cls3 ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.755 ± 0.396 us/op
Microbench.deeplyNested ALWAYS_SPEC 100 4 avgt 3 46.889 ± 18.817 us/op
Microbench.deeplyNested ALWAYS_UNSAFE 100 4 avgt 3 45.166 ± 9.059 us/op
Microbench.deeplyNested ALWAYS_INSTRUMENTATION 100 4 avgt 3 48.041 ± 14.777 us/op
Microbench.justByteArray ALWAYS_SPEC 100 4 avgt 3 0.011 ± 0.008 us/op
Microbench.justByteArray ALWAYS_UNSAFE 100 4 avgt 3 0.011 ± 0.009 us/op
Microbench.justByteArray ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.011 ± 0.007 us/op
Microbench.justByteBuffer ALWAYS_SPEC 100 4 avgt 3 0.053 ± 0.013 us/op
Microbench.justByteBuffer ALWAYS_UNSAFE 100 4 avgt 3 0.053 ± 0.011 us/op
Microbench.justByteBuffer ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.053 ± 0.014 us/op
Microbench.justString ALWAYS_SPEC 100 4 avgt 3 0.055 ± 0.010 us/op
Microbench.justString ALWAYS_UNSAFE 100 4 avgt 3 0.054 ± 0.007 us/op
Microbench.justString ALWAYS_INSTRUMENTATION 100 4 avgt 3 0.054 ± 0.019 us/op
Mentioned for comparison reasons! Performance numbers for the recent version above.
Benchmark (guess) (nested) (refs) Mode Cnt Score Error Units
Microbench.cls1 ALWAYS_SPEC 100 4 avgt 3 0.243 ± 0.170 us/op
Microbench.cls1 ALWAYS_UNSAFE 100 4 avgt 3 0.304 ± 0.078 us/op
Microbench.cls1 NEVER 100 4 avgt 3 0.210 ± 0.126 us/op
Microbench.cls2 ALWAYS_SPEC 100 4 avgt 3 2.924 ± 0.272 us/op
Microbench.cls2 ALWAYS_UNSAFE 100 4 avgt 3 3.086 ± 0.419 us/op
Microbench.cls2 NEVER 100 4 avgt 3 2.624 ± 0.131 us/op
Microbench.cls3 ALWAYS_SPEC 100 4 avgt 3 5.919 ± 1.433 us/op
Microbench.cls3 ALWAYS_UNSAFE 100 4 avgt 3 6.031 ± 1.632 us/op
Microbench.cls3 NEVER 100 4 avgt 3 5.329 ± 0.513 us/op
Microbench.deeplyNested ALWAYS_SPEC 100 4 avgt 3 500.569 ± 27.362 us/op
Microbench.deeplyNested ALWAYS_UNSAFE 100 4 avgt 3 495.529 ± 66.984 us/op
Microbench.deeplyNested NEVER 100 4 avgt 3 388.130 ± 83.949 us/op
Microbench.justByteArray ALWAYS_SPEC 100 4 avgt 3 0.012 ± 0.011 us/op
Microbench.justByteArray ALWAYS_UNSAFE 100 4 avgt 3 0.013 ± 0.004 us/op
Microbench.justByteArray NEVER 100 4 avgt 3 0.055 ± 0.010 us/op
Microbench.justByteBuffer ALWAYS_SPEC 100 4 avgt 3 0.942 ± 0.230 us/op
Microbench.justByteBuffer ALWAYS_UNSAFE 100 4 avgt 3 0.935 ± 0.154 us/op
Microbench.justByteBuffer NEVER 100 4 avgt 3 0.689 ± 0.247 us/op
Microbench.justString ALWAYS_SPEC 100 4 avgt 3 0.616 ± 0.165 us/op
Microbench.justString ALWAYS_UNSAFE 100 4 avgt 3 0.684 ± 0.636 us/op
Microbench.justString NEVER 100 4 avgt 3 0.574 ± 0.098 us/op
MemoryMeter
can use
java.lang.instrument.Instrumentation.getObjectSize()
, which only claims
to provide "approximate" results, but in practice seems to work as
expected.
Two more implementations are available as well. One is using
sun.misc.Unsafe
w/ reflection and one just uses reflection. Both are
inaccurate and rely on guesses. It is strongly recommended to not
use "unsafe" or "specification" and jamm will emit a warning on
stderr:
Unsafe.objectFieldOffset()
, althought the Javadoc says:
Do not expect to perform any sort of arithmetic on this offset; it is just a cookie which is passed to the unsafe heap memory accessors.
The implementation does not always consider Java object layouts in under
all circumstances for all JVMs.MemoryMeter.countChildren()
has been removed, because
there is a) no corresponding implementation from JDK-8249196 and
b) there was no test coverage at all.MemoryMeter
instance has been refactored
to a proper builder pattern to allow distinct classes for each
implementation: Instrumentation, Unsafe, Specification).@Unmetered
for fields has been removed for the future
implementation of java.lang.Runtime.deepSizeOf()
with Java 16.