├── .gitignore
├── README.md
├── license.txt
├── pom.xml
├── src
└── org
│ └── github
│ └── jamm
│ ├── CannotAccessFieldException.java
│ ├── CannotMeasureObjectException.java
│ ├── FieldAndClassFilter.java
│ ├── FieldFilter.java
│ ├── Filters.java
│ ├── IdentityHashSet.java
│ ├── Measurable.java
│ ├── MeasurementStack.java
│ ├── MemoryLayoutSpecification.java
│ ├── MemoryMeter.java
│ ├── MemoryMeterListener.java
│ ├── MemoryMeterStrategy.java
│ ├── Unmetered.java
│ ├── VM.java
│ ├── accessors
│ ├── FieldAccessor.java
│ ├── JpmsAccessor.java
│ └── PlainReflectionAccessor.java
│ ├── listeners
│ ├── NoopMemoryMeterListener.java
│ └── TreePrinter.java
│ ├── strategies
│ ├── ContendedUtils.java
│ ├── ContentionGroupCounter.java
│ ├── DoesNotUseEmptySlotInSuperSpecStrategy.java
│ ├── InstrumentationAndSpecStrategy.java
│ ├── InstrumentationStrategy.java
│ ├── MemoryLayoutBasedStrategy.java
│ ├── MemoryMeterStrategies.java
│ ├── PreJava15SpecStrategy.java
│ ├── PreJava15UnsafeStrategy.java
│ ├── SpecStrategy.java
│ └── UnsafeStrategy.java
│ ├── string
│ ├── PlainReflectionStringMeter.java
│ ├── PreJava9StringMeter.java
│ ├── StringMeter.java
│ └── UnsafeStringMeter.java
│ └── utils
│ ├── ArrayMeasurementUtils.java
│ ├── ByteBufferMeasurementUtils.java
│ ├── MathUtils.java
│ └── MethodHandleUtils.java
├── test
└── org
│ └── github
│ └── jamm
│ ├── GuessTest.java
│ ├── IdentityHashSetTest.java
│ ├── MathUtilsTest.java
│ ├── MemoryMeterTest.java
│ ├── jmh
│ ├── BenchmarkMeasureArray.java
│ ├── BenchmarkMeasureInstance.java
│ ├── BenchmarkMeasureString.java
│ └── BenchmarkObjectGraphTraversal.java
│ ├── strategies
│ ├── MemoryMeterStrategiesTest.java
│ └── MemoryMeterStrategyTest.java
│ ├── string
│ └── StringMeterTest.java
│ └── testedclasses
│ ├── PackageProtectedClass.java
│ └── PublicClassWithPackageProtectedClassField.java
└── toolchains.example.xml
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | out
3 | .gitignore
4 | .idea/
5 | target
6 | *.iml
7 | *.ipr
8 | *.iws
9 | *.orig
10 | *.rej
11 | .classpath
12 | .project
13 | /classes/
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 |
3 | Jamm provides `MemoryMeter`, a Java agent for all Java versions to
4 | measure actual object memory use including JVM overhead.
5 |
6 | Jamm assume that the JVM running the code is an HotSpot JVM. It has not been tested with other type of JVMs.
7 |
8 | # Building
9 |
10 | "mvn package"; optionally, "mvn install"
11 |
12 | # Setup your toolchains.xml
13 |
14 | We want to compile and test using different JVM versions. Configuration option jdkToolchain can be used to supply an alternate
15 | toolchain specification. To achieve our goal we need first to setup `toolchains.xml`.
16 | The `toolchains.xml` file is the configuration file where you set the installation paths of your toolchains. This file
17 | should be put in your `${user.home}/.m2` directory. When the maven-toolchains-plugin executes, it looks for the `toolchains.xml`
18 | file, reads it and looks for a toolchain matching the toolchains requirements configured in the plugin.
19 | Jamm repo contains a `toolchains.example.xml` which you can use as a baseline for your own `toolchains.xml`. You need it
20 | to be able to run the tests. Copy `toolchains.example.xml` to `${user.home}/.m2`, rename the file to `toolchains.xml`.
21 | In `toolchains.xml`, check to update your vendor and jdkHome for JDK8, JDK11 and JDK17 which you have installed on your machine.
22 |
23 | # Running Tests
24 |
25 | The tests can be run with "mvn test". The `JvmArgs` property can be used to specify the JVM arguments that can be used for running the tests.
26 | For example:
27 |
28 | ```
29 | mvn test -DjvmArgs="-Xmx64g"
30 | mvn test -DjvmArgs="-Xmx64g -XX:ObjectAlignmentInBytes=16 -XX:-UseCompressedClassPointers"
31 | ```
32 |
33 | `mvn test` runs all tests with JDK8, JDK11, and then with JDK17
34 | To run the tests with only one particular JDK version run:
35 | * for JDK8:
36 | ```
37 | mvn surefire:test@test-default
38 | ```
39 | or
40 | ```
41 | mvn surefire:test
42 | ```
43 | * for JDK11:
44 | ```
45 | mvn surefire:test@test-jdk11
46 | ```
47 | * for JDK17:
48 | ```
49 | mvn surefire:test@test-jdk17
50 | ```
51 |
52 | # Use
53 |
54 |
55 | The best way to use `MemoryMeter` is to start the JVM with "-javaagent:/jamm.jar" in order to use the
56 | `Instrumentation` strategy to guess objects' sizes.
57 |
58 | `MemoryMeter` can be used in your code like this:
59 |
60 | MemoryMeter meter = MemoryMeter.builder().build();
61 | meter.measure(object);
62 | meter.measureDeep(object);
63 |
64 |
65 | If you would like to use `MemoryMeter` in a web application, make sure
66 | that you do NOT put this jar in `WEB-INF/lib`, as that may cause problems
67 | since your code is accessing a `MemoryMeter` from a different class loader
68 | than the one loaded by the `-javaagent` and won't see it as initialized.
69 |
70 | If you want `MemoryMeter` not to measure some specific classes, you can
71 | mark the classes (or interfaces) using the `Unmetered` annotation.
72 |
73 | # The Maven coordinates for the latest version of Jamm
74 |
75 | ```
76 | com.github.jbellis
77 | jamm
78 | 0.4.0
79 | ```
80 |
81 | # 0.4.0 breaking changes
82 |
83 | The 0.4.0 version comes with speed improvements and support java versions up to Java 17 but also some breaking
84 | changes at the API level.
85 | * The `MemoryMeter` constructor and the static methods used to configure the different options (`withGuessing`, `ignoreOuterClassReference`, `ignoreKnownSingletons`, `ignoreNonStrongReferences`, `enableDebug`) have been removed. Instead `MemoryMeter` instances must be created through a `Builder`.
86 | * The `omitSharedBufferOverhead` option has been removed. Instead a `ByteBufferMode` can be provided as an argument to `measureDeep`. The provided mode must match the way the application is creating SLABs for accurate results.
87 | * The ability to provide a tracker for visited object has been removed.
88 | * `Guess` values have been changed to each represent a single strategy. Fallback strategies can be defined through the `MemoryMeter.Builder::withGuessing` method.
89 | * `MemoryMeter.countChildren` has been removed.
90 | * The `MemoryMeter.measure` and `MemoryMeter.measureDeep` now accept `null` parameters
91 | * Jamm is not trying anymore to support non Hotspot JVM (e.g. OpenJ9)
92 | * By default `MemoryMeter.measureDeep` is now ignoring the space occupied by known singletons such as `Class` objects, `enums`, `ClassLoaders`, `AccessControlContexts` as well as non-strong references
93 | (like weak/soft/phantom references). If you want `MemoryMeter` to measure them you need to enable those measurements through `MemoryMeter.builder().measureKnownSingletons()` and `MemoryMeter.builder().measureNonStrongReferences()`.
94 | * When measuring direct `ByteBuffer` objects `MemoryMeter` is ignoring some fields from the Cleaner as it might lead to some incorrect measurements by including references to other Cleaner instances
95 | * When measuring `Thread` objects `MemoryMeter` is ignoring the `group` field as it references the all the threads from the group
96 | * The behavior around non-strong references has changed. When non-strong references are ignored (the default) `MemoryMeter` will ignore all the fields from the `Reference` class as well as the `head` field from `ReferenceQueue`.
97 |
98 | # Supported Java versions
99 |
100 | The 0.4.0 release has been tested with Java 8, 11 and 17 and the following JVM arguments that can affect the memory layout:
101 | * `-Xmx`
102 | * `UseCompressedClassPointers`
103 | * `ObjectAlignmentInBytes`
104 | * `UseCompressedOops`
105 | * `RestrictContended`
106 | * `EnableContended`
107 | * `ContendedPaddingWidth`
108 | * `UseEmptySlotsInSupers`
109 |
110 | The `Specification` strategy does not work correctly with `UseEmptySlotsInSupers` disabled for some classes (like direct `ByteBuffer`)
111 | that interleave fields from different classes when they should not.
112 |
113 | The `ContendedPaddingWidth` and `EnableContended` arguments are broken in Java 17. Changing the padding width has no effect and disabling @Contended
114 | (`-XX:-EnableContended`) has 2 bugs:
115 | * It does not work for contended annotation on fields
116 | * It does not work for the `ConcurrentHashMap` use of the class Contended annotation
117 |
118 | Those bugs might caused the `Unsafe` and `Specification` strategies to return wrong results when the classes are using `@Contended` and those JVM arguments are used.
119 |
120 | # The fine print
121 |
122 | ## Measurement strategies
123 |
124 | `MemoryMeter` can use different strategies to guess the objects sizes. We have tried to ensure that the output of the strategies is the same.
125 |
126 | ### Instrumentation
127 |
128 | If the JVM has been started with `-javaagent`, `MemoryMeter` will use
129 | `java.lang.instrument.Instrumentation.getObjectSize` to get an estimate of the space required to store
130 | the given object. It is the safest strategy.
131 |
132 | ### Instrumentation and specification
133 |
134 | This strategy requires `java.lang.instrument.Instrumentation` as the `Instrumentation` strategy and will use it
135 | to measure non array object. For measuring arrays it will use the `Specification` strategy way.
136 | This strategy tries to combine the best of both strategies the accuracy and speed of `Instrumentation` for non array object
137 | and the speed of `Specification` for measuring array objects for which all strategy are accurate. For some reason `Instrumentation` is slower for arrays before Java 17.
138 |
139 | ### Unsafe
140 |
141 | `MemoryMeter` will use `Unsafe.objectFieldOffset` to guess the object offset.
142 | Java 14 introduced records and Java 15 introduced Hidden classes which are used from Java 15 onward for Lambda expressions.
143 | Unfortunately, calling `Unsafe.objectFieldOffset` on the `Field` of a record or hidden class will result into an `UnsupportedOperationException` therefore
144 | for record and hidden classes the unsafe strategy delegates the measurement to the specification strategy.
145 |
146 | ### Specification
147 |
148 | `MemoryMeter` will guess the object size based on what it knows from the JVM.
149 |
150 | ## Object graph crawling
151 |
152 | ### Default crawling approach
153 |
154 | When `measureDeep` is called by default `MemoryMeter` will use reflection to crawl the object graph.
155 | In order to prevent infinite loops due to cycles in the object graph `MemoryMeter` tracks visited objects
156 | imposing a memory cost of its own.
157 |
158 | Java 9 introduced the Java Platform Module System (JPMS) that made illegal reflective access between some modules. This is breaking
159 | the ability for Jamm to crawl the object graph. To avoid that problem, if Jamm detects that it cannot use reflection to retrieve
160 | field data it will rely on `Unsafe` to do it. Unfortunately, despite the fact that the code is designed to go around those
161 | illegal accesses the JVM might emit some warning for access that only will be illegal in future versions. The `Unsafe` approach
162 | might also fail for some scenarios as `Unsafe.objectFieldOffset` do not work for `records` or `hidden` classes such
163 | as lambda expressions. In such cases `add-exports` or `add-opens` should be used.
164 |
165 | ### Optimized crawling approach
166 |
167 | For your own classes, `MemoryMeter` provides a way to avoid the use of reflections by having the class implement the `Measurable`
168 | interface. When `MemoryMeter` encounter a class that implements the `Measurable` interface it will call the `addChildrenTo` to let
169 | the class adds its fields to the stack of objects that need to be measured instead of using reflection. Therefore avoiding the reflection cost.
170 |
171 | ### Filtering
172 |
173 | By default `MemoryMeter.measureDeep` is ignoring known singletons such as `Class` objects, `enums`, `ClassLoaders`, `AccessControlContexts` as well as non-strong references
174 | (like weak/soft/phantom references). If you want `MemoryMeter` to measure them you need to enable those measurements through
175 | `MemoryMeter.builder().measureKnownSingletons()` and `MemoryMeter.builder().measureNonStrongReferences()`.
176 |
177 | ## Skipping objects
178 |
179 | If you want `MemoryMeter` not to measure some specific classes or fields, you can
180 | mark the classes/interfaces or fields using the
181 | [`@Unmetered`](./src/org/github/jamm/Unmetered.java) annotation.
182 |
183 | ```
184 | public class WithAnnotationField {
185 |
186 | @Unmetered
187 | private String s;
188 |
189 | public WithAnnotationField(String s) {
190 | this.s = s;
191 | }
192 | ...
193 | }
194 | ```
195 |
196 | ```
197 | @Unmetered
198 | private static class WithTypeAnnotation {
199 | private String s;
200 |
201 | public WithTypeAnnotation(String s) {
202 | this.s = s;
203 | }
204 | ...
205 | }
206 | ```
207 |
208 | For a finer control on which classes and fields should be filtered out it is possible to use the `MemoryMeter(MemoryMeterStrategy, FieldAndClassFilter, FieldFilter , boolean, MemoryMeterListener.Factory)` constructor.
209 |
210 | ## ByteBuffer measurements
211 |
212 | `MemoryMeter` has 3 ways to measure ByteBuffers: `NORMAL`, `SLAB_ALLOCATION_NO_SLICE` and `SLAB_ALLOCATION_SLICE`.
213 |
214 | ### Normal (default) mode
215 |
216 | In this mode `MemoryMeter.measureDeep` will crawl the object graph and sum its different elements.
217 | For a `HeapByteBuffer` like `ByteBuffer.allocate(20)` the crawled graph will be:
218 |
219 | ```
220 | root [java.nio.HeapByteBuffer] 96 bytes (56 bytes)
221 | |
222 | +--hb [byte[]] 40 bytes (40 bytes)
223 | ```
224 | For a `DirectByteBuffer` like `ByteBuffer.allocateDirect(20)` the crawled graph will be:
225 |
226 | ```
227 | root [java.nio.DirectByteBuffer] 136 bytes (64 bytes)
228 | |
229 | +--cleaner [jdk.internal.ref.Cleaner] 72 bytes (40 bytes)
230 | |
231 | +--thunk [java.nio.DirectByteBuffer$Deallocator] 32 bytes (32 bytes)
232 | ```
233 |
234 | In reality the `cleaner` field has some extra fields that `MemoryMeter` is excluding as they might result in an incorrect
235 | measurement. Those fields are:
236 | * `queue` as it is a dummy queue referenced by all `Cleaner` instances
237 | * `next` and `prev` as they are used to create a doubly-linked list of live cleaners and therefore refer to other Cleaners instances
238 |
239 | If `slice`, `duplicate` or `asReadOnlyBuffer` is used to create a new buffer from a heap buffer, the resulting buffer will have a reference to the original buffer array,
240 | whereas if the buffer was a direct buffer, the new buffer would have a direct reference to the original direct buffer:
241 |
242 | ```
243 | root [java.nio.DirectByteBuffer] 200 bytes (64 bytes)
244 | |
245 | +--att [java.nio.DirectByteBuffer] 136 bytes (64 bytes)
246 | |
247 | +--cleaner [jdk.internal.ref.Cleaner] 72 bytes (40 bytes)
248 | |
249 | +--thunk [java.nio.DirectByteBuffer$Deallocator] 32 bytes (32 bytes)
250 | ```
251 | In the `NORMAL` mode those underlying arrays and direct buffer size will always be included in the final total size.
252 |
253 | ### SLAB allocation no slice mode
254 |
255 | The goal of this mode is to omit the size of the shared data in slabs when the slabs are allocated through the use of: `duplicate().position(x).limit(y)`.
256 | This is done by comparing the number of remaining bytes with the buffer capacity and considering only the remaining bytes for the size when the number of remaining bytes is smaller than the capacity.
257 |
258 | ### SLAB allocation slice mode
259 |
260 | The goal of this mode is to omit the size of the shared data in slabs when the slabs are allocated through the use of: `duplicate().position(x).limit(y).slice()`.
261 | This is done by comparing the buffer's capacity with the array'size for heap buffers or with the size of the underlying buffer for direct buffers. If a buffer is considered a slab, only its capacity will considered for the size.
262 |
263 | ## @Contended
264 |
265 | `@Contended` was introduced in Java 8 as `sun.misc.Contended` but was repackaged in the `jdk.internal.vm.annotation` package in Java 9.
266 | Therefore, in Java 9+ unless `-XX:-RestrictContended` or `--add-exports java.base/jdk.internal.vm.annotation=ALL-UNNAMED` are specified `MemoryMeter` will not have access
267 | to the `value()` method of `@Contended` and will be unable to retrieve the contention group tags. Making it potentially unable to computes the correct sizes with the `Unsafe` or `Spec` strategies.
268 | As it also means that only the internal Java classes will use that annotation, `MemoryMeter` will rely on its knowledge of those internal classes to try to go around that problem.
269 |
270 | Moreover as specified in the `Supported Java versions` section the `ContendedPaddingWidth` and `EnableContended` arguments logics are broken in Java 17. Therefore the use of the `ContendedPaddingWidth` argument or of `-XX:-EnableContended` might caused the `Unsafe` and `Specification` strategies to return wrong results when the classes are using `@Contended`.
271 |
272 | ## Non-strong references
273 |
274 | By default `MemoryMeter` will ignore non-strong reference objects. It will also ignore all the Reference's fields and the `head` field of `ReferenceQueue`.
275 | The Reference fields `next` and `discovered` are used by `ReferenceQueue` instances to create a linked list, and taking them into account could lead to measuring a much larger part of the heap than what is usually intended.
276 | The `queue` field is either a singleton `ReferenceQueue.NULL` or a provided queue that users hold a reference to and therefore should be ignored too.
277 | To be consistent, the `head` field of `ReferenceQueue` is also ignored to fully decouple queue and references.
278 | This means that calling measureDeep on a `ReferenceQueue` will by default only measure the `ReferenceQueue` instance and its related objects but not the References that are part of the queue.
279 |
280 | If `measureNonStrongReferences` is set, `MemoryMeter` will measure referenced objects but also all the fields from `Reference` objects and `ReferenceQueue` object.
281 |
282 | ## Debugging
283 |
284 | ### Layout and JVM information
285 |
286 | The `org.github.jamm.strategies.LogInfoAtStartup` system property can be set to `true` to request Jamm to log at startup in `System.out` information about the JVM and the detected memory layout.
287 |
288 | Example of output:
289 |
290 | ```
291 | Jamm starting with: java.version='1.8.0_144', java.vendor='Oracle Corporation', instrumentation=true, unsafe=true, Memory Layout: [objectHeaderSize=12 , arrayHeaderSize=16, objectAlignment=8, referenceSize=4, contendedPaddingWidth=128]
292 | ```
293 | ### Visited object tree
294 |
295 | In order to see the object tree visited when calling `MemoryMeter.measureDeep` and ensuring that it matches your
296 | expectations you can build the `MemoryMeter` instance using `printVisitedTree`:
297 |
298 | ```
299 | MemoryMeter meter = MemoryMeter.builder().printVisitedTree().build();
300 | ```
301 |
302 | If a problem occurs while crawling the graph, `MemoryMeter` will not print the graph in the `System.out` but instead will
303 | print in `System.err` the stack from the object that could not be accessed up to the original input object.
304 |
305 | ## JMH Benchmarks
306 |
307 | The Jamm JMH benchmarks can be run using:
308 |
309 | ```
310 | mvn jmh:benchmark
311 | ```
312 |
313 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ossrh
6 | https://oss.sonatype.org/content/repositories/snapshots
7 |
8 |
9 | 4.0.0
10 | com.github.jbellis
11 | jamm
12 | jar
13 | 0.4.1-SNAPSHOT
14 | Java Agent for Memory Measurements
15 |
16 | Jamm provides MemoryMeter, a java agent to measure actual object memory use including JVM overhead.
17 |
18 | https://github.com/jbellis/jamm/
19 |
20 | scm:git:git://github.com/jbellis/jamm.git
21 | scm:git:git@github.com:jbellis/jamm.git
22 | http://github.com/jbellis/jamm/tree/master/
23 |
24 |
25 | github
26 | http://github.com/jbellis/jamm/issues
27 |
28 |
29 |
30 | The Apache Software License, Version 2.0
31 | http://www.apache.org/licenses/LICENSE-2.0
32 | repo
33 |
34 |
35 |
36 |
37 | junit
38 | junit
39 | 4.12
40 | test
41 |
42 |
43 | org.openjdk.jmh
44 | jmh-core
45 | 1.36
46 | test
47 |
48 |
49 |
50 |
51 |
52 | stephenc
53 | Stephen Connolly
54 |
55 | publisher
56 |
57 |
58 |
59 | jbellis
60 | Jonathan Ellis
61 |
62 | architect
63 |
64 |
65 |
66 | belliottsmith
67 | Benedict Elliott Smith
68 |
69 | developer
70 |
71 |
72 |
73 | mebigfatguy
74 | Dave Brosius
75 |
76 | developer
77 |
78 |
79 |
80 | mshuler
81 | Michael Shuler
82 |
83 | developer
84 |
85 |
86 |
87 | snazy
88 | Robert Stupp
89 |
90 | developer
91 |
92 |
93 |
94 | blerer
95 | Benjamin Lerer
96 |
97 | developer
98 |
99 |
100 |
101 | ekaterinadimitrova2
102 | Ekaterina Dimitrova
103 |
104 | developer
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | ${basedir}/src
113 | ${basedir}/test
114 |
115 |
116 |
117 | maven-clean-plugin
118 | 3.2.0
119 |
120 |
121 | maven-compiler-plugin
122 | 3.11.0
123 |
124 |
125 | maven-deploy-plugin
126 | 3.1.1
127 |
128 |
129 | maven-enforcer-plugin
130 | 3.3.0
131 |
132 |
133 | maven-install-plugin
134 | 3.1.1
135 |
136 |
137 | maven-jar-plugin
138 | 3.3.0
139 |
140 |
141 | maven-resources-plugin
142 | 3.3.0
143 |
144 |
145 | maven-surefire-plugin
146 | 3.0.0-M7
147 |
148 |
149 |
150 |
151 |
152 | maven-compiler-plugin
153 |
154 | 1.8
155 | 1.8
156 |
157 |
158 |
159 | maven-jar-plugin
160 |
161 |
162 |
163 | org.github.jamm.MemoryMeter
164 |
165 |
166 |
167 |
168 |
169 | generate-test-resources
170 |
171 | test-jar
172 |
173 |
174 |
175 |
176 |
177 | maven-surefire-plugin
178 |
179 |
180 | 1.8
181 | Oracle Corporation
182 |
183 | 1
184 | false
185 | ${jvmArgs} -javaagent:${project.build.directory}/${project.build.finalName}-tests.${project.packaging}
186 |
187 |
188 |
189 | test-jdk11
190 |
191 | test
192 |
193 |
194 |
195 | 11
196 |
197 |
198 |
199 |
200 | test-jdk17
201 |
202 | test
203 |
204 |
205 |
206 | 17
207 |
208 |
209 |
210 |
211 |
212 |
213 | pw.krejci
214 | jmh-maven-plugin
215 | 0.2.2
216 |
217 | 1.8
218 | 1.8
219 |
220 |
221 |
222 |
223 |
224 |
225 | release
226 |
227 |
228 |
229 | org.sonatype.plugins
230 | nexus-staging-maven-plugin
231 | 1.6.13
232 | true
233 |
234 | ossrh
235 | https://oss.sonatype.org/
236 | true
237 |
238 |
239 |
240 | org.apache.maven.plugins
241 | maven-source-plugin
242 | 3.3.0
243 |
244 |
245 | attach-sources
246 |
247 | jar-no-fork
248 |
249 |
250 |
251 |
252 |
253 | org.apache.maven.plugins
254 | maven-javadoc-plugin
255 | 3.5.0
256 |
257 |
258 | attach-javadocs
259 |
260 | jar
261 |
262 |
263 |
264 |
265 |
266 | org.apache.maven.plugins
267 | maven-gpg-plugin
268 | 1.5
269 |
270 |
271 | sign-artifacts
272 | verify
273 |
274 | sign
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
--------------------------------------------------------------------------------
/src/org/github/jamm/CannotAccessFieldException.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | /**
4 | * {@code RuntimeException} thrown when Jamm cannot access successfully one of the fields from an object of the measured graph.
5 | */
6 | public class CannotAccessFieldException extends RuntimeException
7 | {
8 | private static final long serialVersionUID = 8558265261116386533L;
9 |
10 | public CannotAccessFieldException(String message) {
11 | super(message);
12 | }
13 |
14 | public CannotAccessFieldException(String message, Throwable cause) {
15 | super(message, cause);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/org/github/jamm/CannotMeasureObjectException.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | /**
4 | * {@code RuntimeException} thrown when Jamm fail to measure an object.
5 | */
6 | public class CannotMeasureObjectException extends RuntimeException
7 | {
8 | private static final long serialVersionUID = -3880720440309336955L;
9 |
10 | public CannotMeasureObjectException(String message)
11 | {
12 | super(message);
13 | }
14 |
15 | public CannotMeasureObjectException(String message, Throwable cause)
16 | {
17 | super(message, cause);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/org/github/jamm/FieldAndClassFilter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | /**
6 | * Filter for fields and classes.
7 | */
8 | @FunctionalInterface
9 | public interface FieldAndClassFilter extends FieldFilter
10 | {
11 | @Override
12 | default boolean ignore(Class> cls, Field field) {
13 | return ignore(field.getType());
14 | }
15 |
16 | /**
17 | * Checks whether a {@code Class} must be ignored or not.
18 | *
19 | * @param clazz the class to check
20 | * @return {@code true} if the class must be ignored {@code false} otherwise.
21 | */
22 | boolean ignore(Class> clazz);
23 | }
24 |
--------------------------------------------------------------------------------
/src/org/github/jamm/FieldFilter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | /**
6 | * A filter for class fields.
7 | */
8 | @FunctionalInterface
9 | public interface FieldFilter
10 | {
11 | /**
12 | * Checks whether a {@code Field} must be ignored or not.
13 | *
14 | * @param cls the class to which the field belong. Which might be different from the declaring class if the field is
15 | * from a superclass.
16 | * @param field the field to check
17 | * @return {@code true} if the field must be ignored {@code false} otherwise.
18 | */
19 | boolean ignore(Class> cls, Field field);
20 | }
21 |
--------------------------------------------------------------------------------
/src/org/github/jamm/Filters.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.lang.ref.Reference;
4 | import java.lang.ref.ReferenceQueue;
5 | import java.lang.reflect.Field;
6 | import java.lang.reflect.Modifier;
7 | import java.nio.ByteBuffer;
8 | import java.security.AccessControlContext;
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | /**
13 | * Utility class providing the different filters used by {@code MemoryMeter}
14 | *
15 | */
16 | public final class Filters
17 | {
18 | /**
19 | * Name pattern for outer class reference
20 | */
21 | private static final String OUTER_CLASS_REFERENCE = "this\\$[0-9]+";
22 |
23 | private static final List CLEANER_FIELDS_TO_IGNORE = Arrays.asList("queue", "prev", "next");
24 |
25 | private static final Class> CLEANER_CLASS = getCleanerClass();
26 |
27 | private static Class> getCleanerClass() {
28 | try {
29 | return ByteBuffer.allocateDirect(0)
30 | .getClass()
31 | .getDeclaredField("cleaner")
32 | .getType();
33 |
34 | } catch (Exception e) {
35 | System.out.print("WARN: Jamm could not load the sun.misc.Cleaner Class. This might lead to overestimating DirectByteBuffer size.");
36 | return null;
37 | }
38 | }
39 |
40 | /**
41 | * Filter excluding static and primitive fields
42 | */
43 | public static final FieldFilter IGNORE_STATIC_AND_PRIMITIVE_FIELDS = (c, f) -> Modifier.isStatic(f.getModifiers()) || f.getType().isPrimitive();
44 |
45 | /**
46 | * Filter excluding class such as {@code Enum}, {@code Class}, {@code ClassLoader} and {@code AccessControlContext}
47 | */
48 | public static final FieldAndClassFilter IGNORE_KNOWN_SINGLETONS = c -> Class.class.equals(c)
49 | || Enum.class.isAssignableFrom(c)
50 | || ClassLoader.class.isAssignableFrom(c)
51 | || AccessControlContext.class.isAssignableFrom(c);
52 |
53 | /**
54 | * Filter excluding all the Reference's fields and the {@code head} field of {@code ReferenceQueue}.
55 | * The Reference fields {@code next} and {@code discovered} are used by {@code ReferenceQueue} instances to create a linked list and are not part of what
56 | * we want to measure. The {@code queue} field is either a singleton {@code ReferenceQueue.NULL} or a provided queue that user hold a reference to and therefore
57 | * should be ignored too. To be consistent, the {@code head} field of {@code ReferenceQueue} is also ignored to fully decouple queue and references.
58 | */
59 | public static final FieldFilter IGNORE_NON_STRONG_REFERENCES = (c, f) ->
60 | f.getDeclaringClass().equals(Reference.class) || (ReferenceQueue.class.isAssignableFrom(c) && "head".equals(f.getName()));
61 |
62 | /**
63 | * Filter excluding some of the fields from sun.misc.Cleaner as they should not be taken into account.
64 | * The fields being excluded are:
65 | *
66 | *
queue: as it is a dummy queue referenced by all Cleaner instances.
67 | *
next and prev: as they are used to create a doubly-linked list of live cleaners and therefore refer to other Cleaners instances
68 | *
69 | */
70 | public static final FieldFilter IGNORE_CLEANER_FIELDS = (c, f) -> c.equals(CLEANER_CLASS) && CLEANER_FIELDS_TO_IGNORE.contains(f.getName()) ;
71 |
72 | /**
73 | * Filter excluding the {@code group} field from thread classes as that field holds the references to all the other threads from the group to which the thread belongs.
74 | */
75 | public static final FieldFilter IGNORE_THREAD_FIELDS = (c, f) -> c.equals(Thread.class) && "group".equals(f.getName()) ;
76 |
77 | /**
78 | * Filter excluding the outer class reference from non-static inner classes.
79 | * In practice that filter is only useful if the top class is an inner class, and we wish to ignore the outer class in the measurement.
80 | */
81 | public static final FieldFilter IGNORE_OUTER_CLASS_REFERENCES = (c, f) -> f.getName().matches(OUTER_CLASS_REFERENCE);
82 |
83 | /**
84 | * Filter excluding fields and class annotated with {@code Unmetered}
85 | */
86 | public static final FieldAndClassFilter IGNORE_UNMETERED_FIELDS_AND_CLASSES = new FieldAndClassFilter()
87 | {
88 | @Override
89 | public boolean ignore(Class> cls, Field field) {
90 | return field.isAnnotationPresent(Unmetered.class) || ignore(field.getType());
91 | }
92 |
93 | @Override
94 | public boolean ignore(Class> cls) {
95 | // The @Inherited annotation only causes annotations to be inherited from superclasses. Therefore we need to check the interfaces manually
96 | return cls != null && (cls.isAnnotationPresent(Unmetered.class) || isAnnotationPresentOnInterfaces(cls));
97 | }
98 |
99 | /**
100 | * Checks if any of the implemented interfaces has the {@code @Unmetered} annotation
101 | * @param cls the class for which the interfaces must be checked
102 | * @return {@code true} if any of the interfaces is annotated with {@code @Unmetered}. {@code false} otherwise.
103 | */
104 | private boolean isAnnotationPresentOnInterfaces(Class> cls) {
105 | Class>[] interfaces = cls.getInterfaces();
106 | for (int i = 0; i < interfaces.length; i++) {
107 | if (cls.getInterfaces()[i].isAnnotationPresent(Unmetered.class))
108 | return true;
109 | }
110 |
111 | return false;
112 | }
113 | };
114 |
115 | public static FieldAndClassFilter getClassFilters(boolean ignoreKnownSingletons) {
116 |
117 | if (ignoreKnownSingletons)
118 | return new FieldAndClassFilter() {
119 |
120 | @Override
121 | public boolean ignore(Class> cls, Field field) {
122 | return IGNORE_KNOWN_SINGLETONS.ignore(cls, field) || IGNORE_UNMETERED_FIELDS_AND_CLASSES.ignore(cls, field);
123 | }
124 |
125 | @Override
126 | public boolean ignore(Class> cls)
127 | {
128 | return IGNORE_KNOWN_SINGLETONS.ignore(cls) || IGNORE_UNMETERED_FIELDS_AND_CLASSES.ignore(cls);
129 | }
130 | };
131 |
132 | return IGNORE_UNMETERED_FIELDS_AND_CLASSES;
133 | }
134 |
135 | public static FieldFilter getFieldFilters(boolean ignoreKnownSingletons,
136 | boolean ignoreOuterClassReference,
137 | boolean ignoreNonStrongReferences) {
138 |
139 | if (ignoreOuterClassReference) {
140 |
141 | if (ignoreNonStrongReferences)
142 | return (c, f) -> IGNORE_STATIC_AND_PRIMITIVE_FIELDS.ignore(c, f)
143 | || getClassFilters(ignoreKnownSingletons).ignore(c, f)
144 | || IGNORE_CLEANER_FIELDS.ignore(c, f)
145 | || IGNORE_THREAD_FIELDS.ignore(c, f)
146 | || IGNORE_NON_STRONG_REFERENCES.ignore(c, f)
147 | || IGNORE_OUTER_CLASS_REFERENCES.ignore(c, f);
148 |
149 | return (c, f) -> IGNORE_STATIC_AND_PRIMITIVE_FIELDS.ignore(c, f)
150 | || getClassFilters(ignoreKnownSingletons).ignore(c, f)
151 | || IGNORE_CLEANER_FIELDS.ignore(c, f)
152 | || IGNORE_THREAD_FIELDS.ignore(c, f)
153 | || IGNORE_OUTER_CLASS_REFERENCES.ignore(c, f);
154 | }
155 |
156 | if (ignoreNonStrongReferences)
157 | return (c, f) -> IGNORE_STATIC_AND_PRIMITIVE_FIELDS.ignore(c, f)
158 | || getClassFilters(ignoreKnownSingletons).ignore(c, f)
159 | || IGNORE_CLEANER_FIELDS.ignore(c, f)
160 | || IGNORE_THREAD_FIELDS.ignore(c, f)
161 | || IGNORE_NON_STRONG_REFERENCES.ignore(c, f);
162 |
163 | return (c, f) -> IGNORE_STATIC_AND_PRIMITIVE_FIELDS.ignore(c, f)
164 | || getClassFilters(ignoreKnownSingletons).ignore(c, f)
165 | || IGNORE_CLEANER_FIELDS.ignore(c, f)
166 | || IGNORE_THREAD_FIELDS.ignore(c, f);
167 | }
168 |
169 | /**
170 | * The class should not be instantiated.
171 | */
172 | private Filters() {
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/org/github/jamm/IdentityHashSet.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | /**
4 | * Simple set that use object equality to compare elements. This set is used in {@code MemoryMeter} to keep track of
5 | * the objects already visited to avoid circular dependencies.
6 | *
7 | *
This class provides constant-time performance for the {@code add} operation,
8 | * assuming the system identity hash function ({@link System#identityHashCode(Object)})
9 | * disperses elements properly among the buckets.
10 | *
11 | *
{@code IdentityHashSet} uses linear probing to resolve hash collisions. When a hash collision occurs it will look
12 | * for the next {@code null} bucket available. To minimize the risk of clustering impacting the performance,
13 | * {@code IdentityHashSet} will ensure that the underlying array is at most 2/3 full by resizing the array when this
14 | * limit is reached.
15 | */
16 | public final class IdentityHashSet
17 | {
18 | int size;
19 | // Open-addressing table for this set.
20 | // This table will never be fully populated (2/3) to keep enough "spare slots" that are `null`
21 | // so a loop checking for an element would not have to check too many slots (iteration stops
22 | // when an entry in the table is `null`).
23 | Object[] table = new Object[32]; // 32 2/3 populated = 21 elements
24 |
25 | boolean add(Object o)
26 | {
27 | // no need for a null-check here, see call-sites
28 | for (; true; resize())
29 | {
30 | Object[] tab = table;
31 | int len = tab.length;
32 | int mask = len - 1;
33 | int i = index(o, mask);
34 |
35 | while (true)
36 | {
37 | Object item = tab[i];
38 | if (item == null)
39 | break;
40 | if (item == o)
41 | return false;
42 | i = inc(i, len);
43 | }
44 |
45 | int s = size + 1;
46 | // Ensure that the array is only at most 2/3 full
47 | // if ( s <= ((2 * len) / 3)
48 | // if ((3 * s) <= (2 * len))
49 | // if ((s + (2 * s)) <= (2 * len))
50 | if (s + (s << 1) <= (len << 1))
51 | {
52 | size = s;
53 | tab[i] = o;
54 | return true;
55 | }
56 | }
57 | }
58 |
59 | private void resize()
60 | {
61 | Object[] tab = table;
62 |
63 | int newLength = tab.length << 1;
64 | if (newLength < 0)
65 | throw new IllegalStateException("too many objects visited");
66 |
67 | Object[] n = new Object[newLength];
68 | int mask = newLength - 1;
69 | int i;
70 | for (Object o : tab)
71 | {
72 | if (o != null)
73 | {
74 | i = index(o, mask);
75 | while (n[i] != null)
76 | i = inc(i, newLength);
77 | n[i] = o;
78 | }
79 | }
80 | table = n;
81 | }
82 |
83 | private static int index(Object o, int mask)
84 | {
85 | return System.identityHashCode(o) & mask;
86 | }
87 |
88 | private static int inc(int i, int len)
89 | {
90 | int n = i + 1;
91 | return n >= len ? 0 : n;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/org/github/jamm/Measurable.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | /**
4 | * Interface that allows users to avoid crawling via reflection by adding children manually to the stack, therefore
5 | * speeding up the computation. The downside of this approach is that it pushes the responsibility to the user to ensure
6 | * that all the children are provided.
7 | */
8 | public interface Measurable {
9 |
10 | /**
11 | * Allow the implementation to pre-compute and cache the {@code Measurable} shallow size.
12 | *
13 | * @param strategy the {@code MemoryMeterStrategy}
14 | * @return the object shallow size
15 | */
16 | default long shallowSize(MemoryMeterStrategy strategy) {
17 | return strategy.measure(this);
18 | }
19 |
20 | /**
21 | * Adds the children that should be part of the measurement to the stack
22 | * @param stack the stack to which the children must be added
23 | */
24 | void addChildrenTo(MeasurementStack stack);
25 | }
26 |
--------------------------------------------------------------------------------
/src/org/github/jamm/MeasurementStack.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.util.ArrayDeque;
4 | import java.util.Deque;
5 |
6 | /**
7 | *
8 | */
9 | public final class MeasurementStack {
10 |
11 | /**
12 | * Tracker used to ensure that we do not visit the same instance twice.
13 | */
14 | private final IdentityHashSet tracker = new IdentityHashSet();
15 |
16 | /**
17 | * The listener
18 | */
19 | private final MemoryMeterListener listener;
20 |
21 | /**
22 | * Filter used to determine which classes should be ignored.
23 | */
24 | private final FieldAndClassFilter classFilter;
25 |
26 | /**
27 | * Stack of objects that need to be measured.
28 | */
29 | private final Deque
101 | *
For more inside on the Java Object Layout, I highly recommend the excellent Java Objects Inside Out blog post from Aleksey Shipilëv.
102 | */
103 | class PreJava15SpecStrategy extends MemoryLayoutBasedStrategy {
104 |
105 | /**
106 | * Align the size of the fields.
107 | *
Prior to JDK 15 the field blocks for each super class were aligned based using the object reference size.
108 | * This method provides a hook for that logic.
109 | *
110 | * @param sizeOfDeclaredFields the size of the fields for a class of the hierarchy
111 | * @return the size of the class fields aligned.
112 | */
113 | protected long alignFieldBlock(long sizeOfDeclaredFields) {
114 | return roundTo(sizeOfDeclaredFields, MEMORY_LAYOUT.getReferenceSize());
115 | }
116 |
117 | @Override
118 | public final long measureInstance(Object instance, Class> type) {
119 |
120 | long size = sizeOfFields(type, false);
121 | return roundTo(size, MEMORY_LAYOUT.getObjectAlignment());
122 | }
123 |
124 | private long sizeOfFields(Class> type, boolean useFieldBlockAlignment) {
125 |
126 | final boolean isContendedEnabled = isContendedEnabled(type);
127 |
128 | if (type == Object.class)
129 | return MEMORY_LAYOUT.getObjectHeaderSize();
130 |
131 | long size = sizeOfFields(type.getSuperclass(), true);
132 |
133 | long sizeOfDeclaredField = 0;
134 | long sizeTakenBy8BytesFields = 0;
135 | ContentionGroupsCounter contentionGroupCounter = null;
136 | for (Field f : type.getDeclaredFields()) {
137 | if (!Modifier.isStatic(f.getModifiers())) {
138 | int fieldSize = measureField(f.getType());
139 |
140 | sizeTakenBy8BytesFields += fieldSize & 8; // count only the 8 bytes fields
141 | sizeOfDeclaredField += fieldSize;
142 | // If some fields are annotated with @Contended we need to count the contention groups to know how much padding needs to be added
143 | if (isContendedEnabled)
144 | contentionGroupCounter = countContentionGroup(contentionGroupCounter, f);
145 | }
146 | }
147 |
148 | // Ensure that we take into account the superclass gaps
149 | if (hasSuperClassGap(size, sizeOfDeclaredField, sizeTakenBy8BytesFields)) {
150 | size = roundTo(size, 8);
151 | }
152 |
153 | // Ensure that we take into account the hierarchy gaps
154 | size += useFieldBlockAlignment ? alignFieldBlock(sizeOfDeclaredField) : sizeOfDeclaredField;
155 |
156 | /* From Contended Javadoc:
157 | * The class level {@code @Contended} annotation is not inherited and has
158 | * no effect on the fields declared in any sub-classes. The effects of all
159 | * {@code @Contended} annotations, however, remain in force for all
160 | * subclass instances, providing isolation of all the defined contention
161 | * groups. Contention group tags are not inherited, and the same tag used
162 | * in a superclass and subclass, represent distinct contention groups.
163 | */
164 | if (isContendedEnabled && isClassAnnotatedWithContended(type))
165 | size += (MEMORY_LAYOUT.getContendedPaddingWidth() << 1);
166 |
167 | if (contentionGroupCounter != null)
168 | size += (contentionGroupCounter.count() + 1) * MEMORY_LAYOUT.getContendedPaddingWidth(); // 1 padding before each group + 1 at the end
169 |
170 | return size;
171 | }
172 |
173 | /**
174 | * Checks if there should be a gap left at the level of the super class fields.
175 | *
176 | * @param size the memory space used by all the super classes fields so far.
177 | * @param sizeOfDeclaredField the size taken by the field of the class being looked at
178 | * @param sizeTakenBy8BytesFields the size taken only by the 8 byte fields
179 | * @return if there should be a gap left at the level of the super class fields
180 | */
181 | protected boolean hasSuperClassGap(long size, long sizeOfDeclaredField, long sizeTakenBy8BytesFields) {
182 | return hasGapSmallerThan8Bytes(size) && hasOnly8BytesFields(sizeOfDeclaredField, sizeTakenBy8BytesFields);
183 | }
184 |
185 | /**
186 | * Checks if the fields for this field block are all 8 bytes fields.
187 | *
188 | * @param sizeOfDeclaredField the total size of the fields within this block
189 | * @param sizeTakenBy8BytesFields the total size taken by the 8 bytes fields within this block
190 | * * @return {@code true} if the fields for this block are all 8 bytes fields, {@code false} otherwise.
191 | */
192 | protected final boolean hasOnly8BytesFields(long sizeOfDeclaredField, long sizeTakenBy8BytesFields) {
193 | return sizeOfDeclaredField != 0 && sizeOfDeclaredField == sizeTakenBy8BytesFields;
194 | }
195 |
196 | /**
197 | * Checks if the previous block has a gap at the end smaller than 8 bytes into which the JVM can store some field
198 | * to improve packing.
199 | *
We know that if there is a gap this gap will always be of 4 bytes as field blocks are always aligned by the
200 | * Object reference size which can only be 4 (compressed reference) or 8.
201 | *
202 | * @param size the size of the memory used so far.
203 | * @return {@code true} if there is some space available (4 bytes), {@code false} otherwise.
204 | */
205 | protected final boolean hasGapSmallerThan8Bytes(long size) {
206 | return (size & 7) > 0;
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/org/github/jamm/strategies/PreJava15UnsafeStrategy.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.strategies;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.reflect.Field;
5 | import java.lang.reflect.Modifier;
6 | import java.util.Optional;
7 |
8 | import org.github.jamm.CannotMeasureObjectException;
9 | import org.github.jamm.VM;
10 |
11 | import static org.github.jamm.strategies.ContendedUtils.isClassAnnotatedWithContended;
12 | import static org.github.jamm.strategies.ContendedUtils.isContendedEnabled;
13 | import static org.github.jamm.strategies.ContendedUtils.isFieldAnnotatedWithContended;
14 |
15 | import sun.misc.Unsafe;
16 |
17 | import static org.github.jamm.utils.MathUtils.roundTo;
18 |
19 | /**
20 | * {@code MemoryMeterStrategy} relying on {@code Unsafe} to measure object sizes for Java version pre-15.
21 | *
22 | *
In Java 15, the JVM layout of fields across the hierarchy changed. Prior to Java 15 superclass field
23 | * always came first. From Java 15 onward it is not the case anymore. This strategy takes advantage and it looks at
24 | * the child class first to find the greatest offsets.
25 | */
26 | final class PreJava15UnsafeStrategy extends MemoryLayoutBasedStrategy {
27 |
28 | private final static Unsafe UNSAFE = VM.getUnsafe();
29 |
30 | private final static int ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(Object[].class);
31 |
32 | /**
33 | * Method Handle for the {@code Class.isRecord} method introduced in Java 14.
34 | */
35 | private final Optional mayBeIsRecordMH;
36 |
37 | /**
38 | * The strategy used for records.
39 | */
40 | private final MemoryLayoutBasedStrategy recordsStrategy;
41 |
42 | public PreJava15UnsafeStrategy(Optional mayBeIsRecordMH,
43 | MemoryLayoutBasedStrategy strategy) {
44 |
45 | this.mayBeIsRecordMH = mayBeIsRecordMH;
46 | this.recordsStrategy = strategy;
47 | }
48 |
49 | @Override
50 | public long measureInstance(Object instance, Class> type) {
51 |
52 | try {
53 |
54 | // If the class is a record 'unsafe.objectFieldOffset(f)' will throw an UnsupportedOperationException
55 | // In those cases, rather than failing, we rely on the Spec strategy to provide the measurement.
56 | if (mayBeIsRecordMH.isPresent() && ((Boolean) mayBeIsRecordMH.get().invoke(type)))
57 | return recordsStrategy.measureInstance(instance, type);
58 |
59 | int annotatedClassesWithoutFields = 0; // Keep track of the @Contended annotated classes without fields
60 | while (type != null) {
61 |
62 | long size = 0;
63 | boolean isLastFieldWithinContentionGroup = false;
64 | for (Field f : type.getDeclaredFields()) {
65 | if (!Modifier.isStatic(f.getModifiers()))
66 | {
67 | long previousSize = size;
68 | size = Math.max(size, UNSAFE.objectFieldOffset(f) + measureField(f.getType()));
69 | if (previousSize < size)
70 | isLastFieldWithinContentionGroup = isFieldAnnotatedWithContended(f) && isContendedEnabled(type);
71 | }
72 | }
73 |
74 | // If the last field is within a contention group we need to add the end padding
75 | if (isLastFieldWithinContentionGroup)
76 | size += MEMORY_LAYOUT.getContendedPaddingWidth();
77 |
78 | // As we know that the superclass fields always come first pre-Java 15, if the size is greater than zero
79 | // we know that all the other fields will have a smaller offset.
80 | if (size > 0) {
81 | // If the class is annotated with @Contended we need to add the end padding
82 | if (isClassAnnotatedWithContended(type) && isContendedEnabled(type))
83 | size += MEMORY_LAYOUT.getContendedPaddingWidth();
84 |
85 | size += annotatedClassesWithoutFields * (MEMORY_LAYOUT.getContendedPaddingWidth() << 1);
86 |
87 | return roundTo(size, MEMORY_LAYOUT.getObjectAlignment());
88 | }
89 |
90 | // The JVM will add padding even if the annotated class does not have any fields
91 | if (isClassAnnotatedWithContended(type) && isContendedEnabled(type))
92 | annotatedClassesWithoutFields++;
93 |
94 | type = type.getSuperclass();
95 | }
96 | return roundTo(MEMORY_LAYOUT.getObjectHeaderSize(), MEMORY_LAYOUT.getObjectAlignment());
97 | }
98 | catch (Throwable e) {
99 | throw new CannotMeasureObjectException("The object of type " + type + " cannot be measured by the unsafe strategy", e);
100 | }
101 | }
102 |
103 | @Override
104 | protected int arrayBaseOffset() {
105 | return ARRAY_BASE_OFFSET;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/org/github/jamm/strategies/SpecStrategy.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.strategies;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 |
6 | import static org.github.jamm.strategies.ContendedUtils.countContentionGroup;
7 | import static org.github.jamm.strategies.ContendedUtils.isClassAnnotatedWithContended;
8 | import static org.github.jamm.strategies.ContendedUtils.isContendedEnabled;
9 | import static org.github.jamm.utils.MathUtils.roundTo;
10 |
11 | /**
12 | * {@code MemoryMeterStrategy} that computes the size of the memory occupied by an object, in a Java 15+ JVM, based on
13 | * the JVM information.
14 | *
In Java 15 the Field layout computation was optimized (https://bugs.openjdk.org/browse/JDK-8237767) to eliminate
15 | * a certain amount of the inefficiency from the previous versions (see {@link PreJava15SpecStrategy}).
16 | */
17 | class SpecStrategy extends MemoryLayoutBasedStrategy {
18 |
19 | @Override
20 | public final long measureInstance(Object instance, Class> type) {
21 |
22 | long size = MEMORY_LAYOUT.getObjectHeaderSize() + sizeOfDeclaredFields(type);
23 | while ((type = type.getSuperclass()) != Object.class && type != null)
24 | size += sizeOfDeclaredFields(type);
25 |
26 | return roundTo(size, MEMORY_LAYOUT.getObjectAlignment());
27 | }
28 |
29 | /**
30 | * Returns the size of the declared fields of the specified class.
31 | *
32 | * @param type the class for which the size of its declared fields must be returned
33 | * @return the size of the declared fields of the specified class
34 | */
35 | private long sizeOfDeclaredFields(Class> type) {
36 |
37 | long size = 0;
38 | ContentionGroupsCounter contentionGroupCounter = null;
39 | for (Field f : type.getDeclaredFields())
40 | {
41 | if (!Modifier.isStatic(f.getModifiers())) {
42 | size += measureField(f.getType());
43 | // If some fields are annotated with @Contended we need to count the contention groups to know how much padding needs to be added
44 | // In Java 17, disabling Contended has no effect on field level annotations
45 | contentionGroupCounter = countContentionGroup(contentionGroupCounter, f);
46 | }
47 | }
48 |
49 | /* From Contended Javadoc:
50 | * The class level {@code @Contended} annotation is not inherited and has
51 | * no effect on the fields declared in any sub-classes. The effects of all
52 | * {@code @Contended} annotations, however, remain in force for all
53 | * subclass instances, providing isolation of all the defined contention
54 | * groups. Contention group tags are not inherited, and the same tag used
55 | * in a superclass and subclass, represent distinct contention groups.
56 | */
57 | if (isClassAnnotatedWithContended(type) && isContendedEnabled(type))
58 | size += (MEMORY_LAYOUT.getContendedPaddingWidth() << 1);
59 |
60 | if (contentionGroupCounter != null)
61 | size += contentionGroupCounter.count() * (MEMORY_LAYOUT.getContendedPaddingWidth() << 1);
62 |
63 | return size;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/org/github/jamm/strategies/UnsafeStrategy.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.strategies;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.reflect.Field;
5 | import java.lang.reflect.Modifier;
6 |
7 | import org.github.jamm.CannotMeasureObjectException;
8 | import org.github.jamm.VM;
9 |
10 | import static org.github.jamm.strategies.ContendedUtils.isClassAnnotatedWithContended;
11 | import static org.github.jamm.strategies.ContendedUtils.isContendedEnabled;
12 | import static org.github.jamm.strategies.ContendedUtils.isFieldAnnotatedWithContended;
13 | import static org.github.jamm.utils.MathUtils.roundTo;
14 |
15 | import sun.misc.Unsafe;
16 |
17 | /**
18 | * {@code MemoryMeterStrategy} relying on {@code Unsafe} to measure object sizes for Java versions ≥ 15.
19 | *
20 | *
In Java 15, the way the JVM layout fields across the hierarchy changed. Prior to Java 15 superclass field
21 | * always came first. Therefore that strategy could only look at the current class to find the greatest offsets.
22 | * From Java 15 onward it is not the case anymore. The JVM optimizes ensure minimal memory usage by packing the fields
23 | * in the best possible way across the hierarchy (https://bugs.openjdk.org/browse/JDK-8237767).
24 | *
25 | *
Another important change that came in Java 15 is the introduction of hidden classes (https://openjdk.org/jeps/371)
26 | * and the use of hidden class for lambda. Attempting to use {@code Unsafe.objectFieldOffset} on an hidden class field
27 | * will result in a {@code UnsupportedOperationException} preventing the {@code UnsafeStrategy} to evaluate correctly
28 | * the memory used by the class. To avoid that problem {@code UnsafeStrategy} will rely on the {@code SpecStrategy} to
29 | * measure hidden classes. This can lead to an overestimation of the object size as the {@code SpecStrategy} ignore some
30 | * optimizations performed by the JVM
31 | */
32 | public final class UnsafeStrategy extends MemoryLayoutBasedStrategy {
33 |
34 | private final static Unsafe UNSAFE = VM.getUnsafe();
35 |
36 | private final static int ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(Object[].class);
37 |
38 | /**
39 | * Method Handle for the {@code Class.isRecord} method introduced in Java 14.
40 | */
41 | private final MethodHandle isRecordMH;
42 |
43 | /**
44 | * Method Handle for the {@code Class.isHidden} method introduced in Java 15.
45 | */
46 | private final MethodHandle isHiddenMH;
47 |
48 | /**
49 | * The strategy used for hidden classes and records.
50 | */
51 | private final MemoryLayoutBasedStrategy hiddenClassesOrRecordsStrategy;
52 |
53 | public UnsafeStrategy(MethodHandle isRecordMH,
54 | MethodHandle isHiddenMH,
55 | MemoryLayoutBasedStrategy strategy) {
56 | this.isRecordMH = isRecordMH;
57 | this.isHiddenMH = isHiddenMH;
58 | this.hiddenClassesOrRecordsStrategy = strategy;
59 | }
60 |
61 | @Override
62 | public long measureInstance(Object instance, Class> type) {
63 |
64 | try {
65 |
66 | // If the class is a hidden class ore a record 'unsafe.objectFieldOffset(f)' will throw an UnsupportedOperationException
67 | // In those cases, rather than failing, we rely on the Spec strategy to provide the measurement.
68 | if ((Boolean) isRecordMH.invoke(type) || (Boolean) isHiddenMH.invoke(type))
69 | return hiddenClassesOrRecordsStrategy.measureInstance(instance, type);
70 |
71 | long size = 0;
72 | boolean isLastFieldWithinContentionGroup = false;
73 | while (type != null)
74 | {
75 | for (Field f : type.getDeclaredFields()) {
76 | if (!Modifier.isStatic(f.getModifiers())) {
77 | long previousSize = size;
78 | size = Math.max(size, UNSAFE.objectFieldOffset(f) + measureField(f.getType()));
79 | // In Java 17, disabling Contended has no effect on field level annotations
80 | if (previousSize < size)
81 | isLastFieldWithinContentionGroup = isFieldAnnotatedWithContended(f);
82 | }
83 | }
84 |
85 | if (isClassAnnotatedWithContended(type) && isContendedEnabled(type))
86 | size += MEMORY_LAYOUT.getContendedPaddingWidth();
87 |
88 | type = type.getSuperclass();
89 | }
90 | if (size == 0) {
91 | size = MEMORY_LAYOUT.getObjectHeaderSize();
92 | } else {
93 | if (isLastFieldWithinContentionGroup)
94 | size += MEMORY_LAYOUT.getContendedPaddingWidth();
95 | }
96 | size = size > 0 ? size : MEMORY_LAYOUT.getObjectHeaderSize();
97 | return roundTo(size, MEMORY_LAYOUT.getObjectAlignment());
98 | }
99 | catch (Throwable e) {
100 | throw new CannotMeasureObjectException("The object of type " + type + " cannot be measured by the unsafe strategy", e);
101 | }
102 | }
103 |
104 | @Override
105 | protected int arrayBaseOffset() {
106 | return ARRAY_BASE_OFFSET;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/org/github/jamm/string/PlainReflectionStringMeter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.string;
2 |
3 | import java.lang.invoke.MethodHandle;
4 |
5 | import org.github.jamm.CannotAccessFieldException;
6 | import org.github.jamm.MemoryMeterStrategy;
7 |
8 | /**
9 | * {@code StringMeter} used to measure Java 9+ strings where the {@code String} value is a {@code byte} array.
10 | *
11 | */
12 | final class PlainReflectionStringMeter extends StringMeter {
13 |
14 | /**
15 | * The method handle used to access the value field.
16 | */
17 | private final MethodHandle valueMH;
18 |
19 | public PlainReflectionStringMeter(MethodHandle valueMH) {
20 | this.valueMH = valueMH;
21 | }
22 |
23 | @Override
24 | public long measureStringValue(MemoryMeterStrategy strategy, String s) {
25 | try {
26 | return strategy.measureArray((byte[]) valueMH.invoke(s));
27 | } catch (Throwable e) {
28 | throw new CannotAccessFieldException("The value of the value field from java.lang.String cannot be retrieved", e);
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/org/github/jamm/string/PreJava9StringMeter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.string;
2 |
3 | import java.lang.invoke.MethodHandle;
4 |
5 | import org.github.jamm.CannotAccessFieldException;
6 | import org.github.jamm.MemoryMeterStrategy;
7 |
8 | /**
9 | * {@code StringMeter} used to measure pre-Java 9 strings where the {@code String} value is a {@code char} array.
10 | *
11 | */
12 | final class PreJava9StringMeter extends StringMeter {
13 |
14 | /**
15 | * The method handle used to access the value field.
16 | */
17 | private final MethodHandle valueMH;
18 |
19 | public PreJava9StringMeter(MethodHandle valueMH) {
20 | this.valueMH = valueMH;
21 | }
22 |
23 | @Override
24 | public long measureStringValue(MemoryMeterStrategy strategy, String s) {
25 |
26 | if (strategy.supportComputeArraySize())
27 | return strategy.computeArraySize(s.length(), Character.BYTES);
28 |
29 | try {
30 | return strategy.measureArray((char[]) valueMH.invoke(s));
31 | } catch (Throwable e) {
32 | throw new CannotAccessFieldException("The value of the 'value' field from java.lang.String cannot be retrieved", e);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/org/github/jamm/string/StringMeter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.string;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.reflect.Field;
5 | import java.util.Optional;
6 |
7 | import sun.misc.Unsafe;
8 |
9 | import org.github.jamm.CannotAccessFieldException;
10 | import org.github.jamm.MemoryMeter;
11 | import org.github.jamm.MemoryMeterStrategy;
12 | import org.github.jamm.VM;
13 | import org.github.jamm.strategies.MemoryMeterStrategies;
14 | import org.github.jamm.utils.MethodHandleUtils;
15 |
16 | import static org.github.jamm.utils.MethodHandleUtils.methodHandle;
17 |
18 | /**
19 | * Utility to measure the value field of a {@code String}
20 | */
21 | public abstract class StringMeter {
22 |
23 | /**
24 | * Enable or disable string optimization through the {@code org.github.jamm.string.Optimize} String system property. {@code true} by default.
25 | */
26 | public static final boolean ENABLED = Boolean.parseBoolean(System.getProperty("org.github.jamm.string.Optimize", "true"));
27 |
28 | /**
29 | * The String shallow size stored as a constant to have it compiled directly into the measure method.
30 | */
31 | public static final long STRING_SHALLOW_SIZE = MemoryMeterStrategies.getInstance()
32 | .getStrategy(MemoryMeter.BEST)
33 | .measure("");
34 |
35 | /**
36 | * Measure the deep size of the specified String.
37 | *
38 | * @param strategy the strategy to perform the measurement
39 | * @param s the string
40 | * @return the size of the deep string
41 | */
42 | public long measureDeep(MemoryMeterStrategy strategy, String s) {
43 | return STRING_SHALLOW_SIZE + measureStringValue(strategy, s);
44 | }
45 |
46 | /**
47 | * Measure the size of the value of the specified String.
48 | *
49 | * @param strategy the strategy to perform the measurement
50 | * @param s the string
51 | * @return the size of the string value field
52 | */
53 | protected abstract long measureStringValue(MemoryMeterStrategy strategy, String s);
54 |
55 | /**
56 | * Creates a new {@code StringMeter} instance.
57 | * @return a new {@code StringMeter} instance.
58 | */
59 | public static StringMeter newInstance() {
60 |
61 | try {
62 | Field field = String.class.getDeclaredField("value");
63 |
64 | Optional mayBeTrySetAccessible = MethodHandleUtils.mayBeMethodHandle(Field.class, "trySetAccessible"); // Added in Java 9
65 | if (mayBeTrySetAccessible.isPresent()) {
66 |
67 | // Base on the JMH benchmarks, Unsafe is faster than using a MethodHandle so we try to use Unsafe first and default to reflection if it is unavailable.
68 | Unsafe unsafe = VM.getUnsafe();
69 |
70 | if (unsafe == null) {
71 |
72 | if ((boolean) mayBeTrySetAccessible.get().invoke(field)) {
73 | return new PlainReflectionStringMeter(methodHandle(field));
74 | }
75 | throw new CannotAccessFieldException("The value of the 'value' field from java.lang.String"
76 | + " cannot be retrieved as the field cannot be made accessible and Unsafe is unavailable");
77 | }
78 |
79 | long valueFieldOffset = unsafe.objectFieldOffset(field);
80 | return new UnsafeStringMeter(unsafe, valueFieldOffset);
81 | }
82 |
83 | field.setAccessible(true);
84 | return new PreJava9StringMeter(methodHandle(field));
85 |
86 | } catch (Throwable e) {
87 | throw new CannotAccessFieldException("The value of the 'value' field from java.lang.String cannot be retrieved", e);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/org/github/jamm/string/UnsafeStringMeter.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.string;
2 |
3 | import org.github.jamm.MemoryMeterStrategy;
4 |
5 | import sun.misc.Unsafe;
6 |
7 | /**
8 | * {@code StringMeter} using {@code Unsafe} to measure the String value on Java 9+ versions when the field is not
9 | * accessible through reflection.
10 | */
11 | final class UnsafeStringMeter extends StringMeter {
12 |
13 | private final Unsafe unsafe;
14 |
15 | private final long valueFieldOffset;
16 |
17 | UnsafeStringMeter(Unsafe unsafe, long valueFieldOffset) {
18 | this.unsafe = unsafe;
19 | this.valueFieldOffset = valueFieldOffset;
20 | }
21 |
22 | @Override
23 | public long measureStringValue(MemoryMeterStrategy strategy, String s) {
24 | return strategy.measureArray((byte[]) unsafe.getObjectVolatile(s, valueFieldOffset));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/org/github/jamm/utils/ArrayMeasurementUtils.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.utils;
2 |
3 | import static org.github.jamm.utils.MathUtils.roundTo;
4 |
5 | /**
6 | * Utility methods to measure arrays.
7 | */
8 | public final class ArrayMeasurementUtils {
9 |
10 | /**
11 | * Computes the size of an array from its base offset, length, elementSize and object alignment.
12 | *
13 | * @param arrayBaseOffset the array base offset
14 | * @param length the array length
15 | * @param elementSize the size of the array elements
16 | * @param objectAlignment the object alignment (padding) in bytes
17 | * @return the size of the array
18 | */
19 | public static long computeArraySize(int arrayBaseOffset, int length, int elementSize, int objectAlignment) {
20 | return roundTo(arrayBaseOffset + length * (long) elementSize, objectAlignment);
21 | }
22 |
23 | private ArrayMeasurementUtils() {
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/org/github/jamm/utils/ByteBufferMeasurementUtils.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.utils;
2 |
3 | import java.lang.reflect.Field;
4 | import java.nio.ByteBuffer;
5 |
6 | import org.github.jamm.VM;
7 | import org.github.jamm.accessors.FieldAccessor;
8 |
9 | /**
10 | * Utility methods for {@code ByteBuffers} measurements.
11 | */
12 | public final class ByteBufferMeasurementUtils {
13 |
14 | /**
15 | * The field used to store heap ByteBuffer underlying array.
16 | */
17 | private static final Field HB_FIELD = getDeclaredField(ByteBuffer.class, "hb");
18 |
19 | /**
20 | * The field used to store direct ByteBuffer attachment.
21 | */
22 | private static final Field ATT_FIELD = getDeclaredField(ByteBuffer.allocateDirect(0).getClass(), "att");
23 |
24 | /**
25 | * Retrieves the underlying capacity of the specified buffer.
26 | *
27 | * @param buffer the buffer
28 | * @param accessor the field accessor for this java version
29 | * @return the underlying capacity of the specified buffer.
30 | */
31 | public static int underlyingCapacity(ByteBuffer buffer, FieldAccessor accessor) {
32 |
33 | if (buffer.isDirect()) {
34 |
35 | if (buffer.isReadOnly() && VM.isPreJava12JVM()) {
36 | // Pre-java 12, a DirectByteBuffer created from another DirectByteBuffer was using the source buffer as an attachment
37 | // for liveness rather than the source buffer's attachment (https://bugs.openjdk.org/browse/JDK-8208362)
38 | // Therefore for checking slices we need to go one level deeper.
39 | buffer = (ByteBuffer) accessor.getFieldValue(buffer, ATT_FIELD);
40 | }
41 | ByteBuffer att = (ByteBuffer) accessor.getFieldValue(buffer, ATT_FIELD);
42 | return att == null ? buffer.capacity() : att.capacity();
43 | }
44 |
45 | if (buffer.isReadOnly()) {
46 | byte[] hb = (byte[]) accessor.getFieldValue(buffer, HB_FIELD);
47 | return hb.length;
48 | }
49 |
50 | return buffer.array().length;
51 | }
52 |
53 | /**
54 | * Returns the declared field with the specified name for the given class.
55 | *
56 | * @param cls the class
57 | * @param fieldName the field name
58 | * @return the declared field
59 | */
60 | private static Field getDeclaredField(Class> cls, String fieldName) {
61 | try {
62 | return cls.getDeclaredField(fieldName);
63 | } catch (Exception e) {
64 | throw new IllegalStateException(e);
65 | }
66 | }
67 |
68 | private ByteBufferMeasurementUtils() {
69 |
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/org/github/jamm/utils/MathUtils.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.utils;
2 |
3 | public final class MathUtils
4 | {
5 | /**
6 | * Rounds x up to the next multiple of m.
7 | *
8 | * @param x the number to round
9 | * @param m the multiple (must be a power of 2)
10 | * @return the rounded value of x up to the next multiple of m.
11 | */
12 | public static long roundTo(long x, int m) {
13 | return (x + m - 1) & -m;
14 | }
15 |
16 | /**
17 | * Rounds x up to the next multiple of m.
18 | *
19 | * @param x the number to round
20 | * @param m the multiple (must be a power of 2)
21 | * @return the rounded value of x up to the next multiple of m.
22 | */
23 | public static int roundTo(int x, int m) {
24 | return (x + m - 1) & -m;
25 | }
26 |
27 | private MathUtils()
28 | {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/org/github/jamm/utils/MethodHandleUtils.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.utils;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.reflect.Field;
6 | import java.lang.reflect.Method;
7 | import java.util.Optional;
8 |
9 | public final class MethodHandleUtils
10 | {
11 | /**
12 | * Returns the {@code MethodHandle} for the specified class and method if the method exists.
13 | *
14 | * @param klass the class
15 | * @param methodName the method name
16 | * @return an {@code Optional} for the {@code MethodHandle}
17 | */
18 | public static Optional mayBeMethodHandle(Class> klass, String methodName) {
19 | try {
20 | Method method = klass.getMethod(methodName, new Class[0]);
21 | return Optional.of(methodHandle(method));
22 | } catch (Exception e) {
23 | return Optional.empty();
24 | }
25 | }
26 |
27 | /**
28 | * Returns the {@code MethodHandle} for the specified method.
29 | *
30 | * @param method the method
31 | * @return the {@code MethodHandle} for the specified method
32 | * @throws IllegalAccessException if the method is not accessible
33 | */
34 | public static MethodHandle methodHandle(Method method) throws IllegalAccessException {
35 | return MethodHandles.lookup().unreflect(method);
36 | }
37 |
38 | /**
39 | * Returns the {@code MethodHandle} for the specified field.
40 | *
41 | * @param field the field
42 | * @return the {@code MethodHandle} for the specified field
43 | * @throws IllegalAccessException if the method is not accessible
44 | */
45 | public static MethodHandle methodHandle(Field field) throws IllegalAccessException {
46 | return MethodHandles.lookup().unreflectGetter(field);
47 | }
48 |
49 | private MethodHandleUtils() {}
50 | }
51 |
--------------------------------------------------------------------------------
/test/org/github/jamm/IdentityHashSetTest.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.junit.Test;
7 |
8 | import static org.junit.Assert.*;
9 |
10 | public class IdentityHashSetTest {
11 | @Test
12 | public void addSame() {
13 | IdentityHashSet s = new IdentityHashSet();
14 | Object o = new Object();
15 | assertTrue(s.add(o));
16 | assertFalse(s.add(o));
17 | assertEquals(1, s.size);
18 | assertEquals(32, s.table.length);
19 | }
20 |
21 | @Test
22 | public void addMany() {
23 | List ref1 = new ArrayList<>();
24 | List ref2 = new ArrayList<>();
25 | for (int i = 0; i < 250; i++)
26 | assertTrue(ref1.add("o" + i));
27 | for (int i = 0; i < 250; i++)
28 | assertTrue(ref2.add("x" + i));
29 |
30 | IdentityHashSet s = new IdentityHashSet();
31 | for (int i = 0; i < ref1.size(); i++) {
32 | Object o = ref1.get(i);
33 | assertEquals(i, s.size);
34 | assertEquals("" + i, expectedCapacity(i), s.table.length);
35 | assertTrue(s.add(o));
36 | }
37 | assertEquals(ref1.size(), s.size);
38 |
39 | for (Object o : ref1)
40 | assertFalse(s.add(o));
41 |
42 | for (int i = 0; i < ref2.size(); i++) {
43 | Object o = ref2.get(i);
44 | assertEquals(ref1.size() + i, s.size);
45 | assertTrue(s.add(o));
46 | }
47 | assertEquals(ref1.size() + ref2.size(), s.size);
48 |
49 | for (Object o : ref1)
50 | assertFalse(s.add(o));
51 |
52 | for (Object o : ref2)
53 | assertFalse(s.add(o));
54 | }
55 |
56 | private static int expectedCapacity(int i) {
57 | // the backing array must be at most 2/3 full in order to have enough 'null's in the open-addressing-map
58 | i = (i * 3) / 2;
59 |
60 | // next power of 2
61 | i = 1 << (32 - Integer.numberOfLeadingZeros(i));
62 | return Math.max(32, i);
63 | }
64 | }
--------------------------------------------------------------------------------
/test/org/github/jamm/MathUtilsTest.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.github.jamm.utils.MathUtils.roundTo;
6 | import static org.junit.Assert.*;
7 |
8 | public class MathUtilsTest
9 | {
10 | @Test
11 | public void testRoundToInt() {
12 |
13 | assertEquals(0, roundTo(0, 8));
14 | assertEquals(8, roundTo(1, 8));
15 | assertEquals(8, roundTo(2, 8));
16 | assertEquals(8, roundTo(3, 8));
17 | assertEquals(8, roundTo(8, 8));
18 | assertEquals(16, roundTo(9, 8));
19 | assertEquals(16, roundTo(10, 8));
20 | assertEquals(40, roundTo(36, 8));
21 |
22 | assertEquals(0, roundTo(0, 16));
23 | assertEquals(16, roundTo(1, 16));
24 | assertEquals(16, roundTo(2, 16));
25 | assertEquals(16, roundTo(3, 16));
26 | assertEquals(16, roundTo(8, 16));
27 | assertEquals(16, roundTo(9, 16));
28 | assertEquals(16, roundTo(10, 16));
29 | assertEquals(32, roundTo(20, 16));
30 | assertEquals(48, roundTo(36, 16));
31 | }
32 |
33 | @Test
34 | public void testRoundToLong() {
35 |
36 | assertEquals(0, roundTo(0L, 8));
37 | assertEquals(8, roundTo(1L, 8));
38 | assertEquals(8, roundTo(2L, 8));
39 | assertEquals(8, roundTo(3L, 8));
40 | assertEquals(8, roundTo(8L, 8));
41 | assertEquals(16, roundTo(9L, 8));
42 | assertEquals(16, roundTo(10L, 8));
43 | assertEquals(40, roundTo(36L, 8));
44 |
45 | assertEquals(0, roundTo(0L, 16));
46 | assertEquals(16, roundTo(1L, 16));
47 | assertEquals(16, roundTo(2L, 16));
48 | assertEquals(16, roundTo(3L, 16));
49 | assertEquals(16, roundTo(8L, 16));
50 | assertEquals(16, roundTo(9L, 16));
51 | assertEquals(16, roundTo(10L, 16));
52 | assertEquals(32, roundTo(20L, 16));
53 | assertEquals(48, roundTo(36L, 16));
54 | }
55 |
56 | @Test
57 | public void testModulo() {
58 |
59 | assertEquals(0, 0 & 7);
60 | assertEquals(1, 1 & 7);
61 | assertEquals(2, 2 & 7);
62 | assertEquals(5, 5 & 7);
63 | assertEquals(0, 8 & 7);
64 | assertEquals(1, 9 & 7);
65 | assertEquals(4, 12 & 7);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/test/org/github/jamm/MemoryMeterTest.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm;
2 |
3 | import java.lang.ref.PhantomReference;
4 | import java.lang.ref.ReferenceQueue;
5 | import java.lang.ref.SoftReference;
6 | import java.util.Arrays;
7 | import java.util.Collection;
8 | import java.util.Date;
9 | import java.util.function.Predicate;
10 |
11 | import org.junit.Assert;
12 | import org.junit.BeforeClass;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.junit.runners.Parameterized;
16 |
17 | import org.github.jamm.testedclasses.PublicClassWithPackageProtectedClassField;
18 |
19 | import static org.junit.Assert.assertEquals;
20 | import static org.junit.Assert.assertNotEquals;
21 |
22 | @RunWith(Parameterized.class)
23 | public class MemoryMeterTest {
24 |
25 | @BeforeClass
26 | public static void logInfoAtStartup() {
27 | System.setProperty("org.github.jamm.strategies.LogInfoAtStartup", "true");
28 | }
29 |
30 | @Parameterized.Parameters
31 | public static Collection guesses() {
32 |
33 | return Arrays.asList(MemoryMeter.Guess.INSTRUMENTATION,
34 | MemoryMeter.Guess.INSTRUMENTATION_AND_SPECIFICATION,
35 | MemoryMeter.Guess.UNSAFE,
36 | MemoryMeter.Guess.SPECIFICATION);
37 | }
38 |
39 | private final MemoryMeter.Guess guess;
40 |
41 | public MemoryMeterTest(MemoryMeter.Guess guess)
42 | {
43 | this.guess = guess;
44 | }
45 |
46 | @Test
47 | public void testWithNull() {
48 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
49 |
50 | assertEquals(0L, meter.measure(null));
51 | assertEquals(0L, meter.measureDeep(null));
52 | }
53 |
54 | @Test
55 | public void testCycle() throws Exception {
56 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
57 |
58 | Recursive dummy = new Recursive();
59 | long shallowSize = meter.measure(dummy);
60 | assertEquals("Deep size of Recursive is shallow size when child==null", shallowSize, meter.measureDeep(dummy));
61 | dummy.child = dummy;
62 | assertEquals("Deep size of Recursive is shallow size when child==this", shallowSize, meter.measureDeep(dummy));
63 | }
64 |
65 | @Test
66 | public void testDeep() {
67 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
68 |
69 | Recursive root = new Recursive();
70 | Recursive recursive = root;
71 | for (int i = 0; i < 100000; i++) {
72 | recursive.child = new Recursive();
73 | recursive = recursive.child;
74 | }
75 | long shallowSize = meter.measure(root);
76 | assertEquals(shallowSize * 100001, meter.measureDeep(root));
77 | }
78 |
79 | @SuppressWarnings("unused")
80 | private static class Recursive {
81 | int i;
82 | Recursive child = null;
83 | }
84 |
85 | @SuppressWarnings("unused")
86 | public static class Outer {
87 | public int[] somethingHeavy = new int[100];
88 | public Inner inner = new Inner();
89 |
90 | private class Inner {
91 | public int integer = 1;
92 | }
93 | }
94 |
95 | @Test
96 | public void testWithInnerClass () {
97 | Outer outer = new Outer();
98 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
99 |
100 | long outerSize = meter.measure(outer);
101 | long innerSize = meter.measure(outer.inner);
102 | long somethingHeavySize = meter.measure(outer.somethingHeavy);
103 |
104 | long size = outerSize + innerSize + somethingHeavySize;
105 |
106 | assertEquals(size, meter.measureDeep(outer));
107 |
108 | meter = MemoryMeter.builder().withGuessing(guess).ignoreOuterClassReference().build();
109 |
110 | assertEquals(innerSize, meter.measureDeep(outer.inner));
111 | }
112 |
113 | @Test
114 | public void testMeasureKnownSingletons() {
115 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).measureKnownSingletons().build();
116 |
117 | long classFieldSize = meter.measureDeep(new HasClassField());
118 | long enumFieldSize = meter.measureDeep(new HasEnumField());
119 |
120 | meter = MemoryMeter.builder().withGuessing(guess).build();
121 |
122 | assertNotEquals(classFieldSize, meter.measureDeep(new HasClassField()));
123 | assertNotEquals(enumFieldSize, meter.measureDeep(new HasEnumField()));
124 | }
125 |
126 | @Test
127 | public void testClassFilteringWithObjectField() {
128 | // Measure excluding class and enum object
129 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
130 |
131 | HasObjectField withClass = new HasObjectField(String.class);
132 | HasObjectField withString = new HasObjectField(1);
133 |
134 | assertEquals(meter.measure(withClass), meter.measureDeep(withClass));
135 | assertEquals(meter.measure(withString) + meter.measure(1), meter.measureDeep(withString));
136 | }
137 |
138 | @Test
139 | public void testIgnoreNonStrongReferences() throws InterruptedException {
140 | // The Reference fields 'next' and 'discovered' are used by 'ReferenceQueue' instances and are not part of what
141 | // we want to measure. The 'queue' field is either a singleton 'ReferenceQueue.NULL' or a provided queue that user hold a reference to.
142 | // Therefore, those fields should be ignored.
143 | MemoryMeter meterIgnoring = MemoryMeter.builder().withGuessing(guess).build();
144 | MemoryMeter meterMeasuring = MemoryMeter.builder().withGuessing(guess).measureNonStrongReferences().build();
145 |
146 | // test SoftReference
147 | SoftReference ref = new SoftReference(new Date());
148 | long refShallowSize = meterIgnoring.measure(ref);
149 | assertEquals(refShallowSize, meterIgnoring.measureDeep(ref));
150 |
151 | // SoftReference + deep queue + referent
152 | long deepSize = refShallowSize + meterMeasuring.measureDeep(new ReferenceQueue()) + meterMeasuring.measure(new Date());
153 | assertEquals(deepSize, meterMeasuring.measureDeep(ref));
154 |
155 | HasReferenceField hasReferenceField = new HasReferenceField(ref);
156 | long hasReferenceFieldShallowSize = meterIgnoring.measure(hasReferenceField);
157 | assertEquals(refShallowSize + hasReferenceFieldShallowSize, meterIgnoring.measureDeep(hasReferenceField));
158 |
159 | // HasReferenceField + SoftReference + deep queue + referent
160 | deepSize = hasReferenceFieldShallowSize + refShallowSize + meterMeasuring.measureDeep(new ReferenceQueue()) + meterMeasuring.measure(new Date());
161 | assertEquals(deepSize, meterMeasuring.measureDeep(hasReferenceField));
162 |
163 | // Test ReferenceQueue measurement with one object
164 | ReferenceQueue queue = new ReferenceQueue<>();
165 | MyPhantomReference p = new MyPhantomReference(new Object(), queue);
166 |
167 | refShallowSize = meterIgnoring.measure(p);
168 | assertEquals(refShallowSize, meterIgnoring.measureDeep(p));
169 |
170 | // PhantomReference + deep queue + referent
171 | deepSize = refShallowSize + meterMeasuring.measureDeep(new ReferenceQueue()) + meterMeasuring.measure(new Object());
172 | assertEquals(deepSize, meterMeasuring.measureDeep(p));
173 |
174 | System.gc();
175 | // wait for garbage collection
176 | long start = System.currentTimeMillis();
177 | while (!p.isEnqueued() && (System.currentTimeMillis() - start) < 1000)
178 | Thread.yield();
179 |
180 | Assert.assertTrue(p.isEnqueued());
181 |
182 | long queueShallowSize = meterIgnoring.measure(queue);
183 | long lockSize = meterIgnoring.measure(new Object()); // The ReferenceQueue lock field has the same size as an empty object.
184 | assertEquals(queueShallowSize + lockSize, meterIgnoring.measureDeep(queue));
185 |
186 | assertEquals(p, queue.poll());
187 | }
188 |
189 | private static class MyPhantomReference extends PhantomReference {
190 |
191 | public MyPhantomReference(Object referent, ReferenceQueue q) {
192 | super(referent, q);
193 | }
194 | }
195 |
196 | @SuppressWarnings("unused")
197 | private static class HasObjectField {
198 | private Object obj = String.class;
199 |
200 | public HasObjectField(Object obj) {
201 | this.obj = obj;
202 | }
203 | }
204 |
205 | @SuppressWarnings("unused")
206 | private static class HasClassField {
207 | private Class> cls = String.class;
208 | }
209 |
210 | @SuppressWarnings("unused")
211 | private static class HasEnumField {
212 | enum Giant {Fee, Fi, Fo, Fum}
213 |
214 | private Giant grunt = Giant.Fee;
215 | }
216 |
217 | @SuppressWarnings("unused")
218 | private static class HasReferenceField {
219 | private final SoftReference ref;
220 |
221 | public HasReferenceField(SoftReference ref) {
222 | this.ref = ref;
223 | }
224 | }
225 |
226 | @Test
227 | public void testUnmeteredAnnotationOnFields() {
228 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
229 |
230 | String s = "test";
231 |
232 | long stringDeepSize = meter.measureDeep(s);
233 | long shallowSize = meter.measure(new WithoutAnnotationField(null));
234 | assertEquals(stringDeepSize + shallowSize, meter.measureDeep(new WithoutAnnotationField(s)));
235 |
236 | shallowSize = meter.measure(new WithAnnotationField(null));
237 | assertEquals(shallowSize, meter.measureDeep(new WithAnnotationField(s)));
238 | }
239 |
240 | @Test
241 | public void testUnmeteredTypeAnnotation() {
242 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
243 |
244 | String s = "test";
245 | assertEquals(0, meter.measureDeep(new ClassWithAnnotation(s)));
246 | }
247 |
248 | @Test
249 | public void testUnmeteredAnnotationOnParent() {
250 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
251 |
252 | assertEquals(0, meter.measureDeep(new WithParentWithAnnotation("test")));
253 | }
254 |
255 | @Test
256 | public void testUnmeteredAnnotationOnImplementedInteface() {
257 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
258 |
259 | assertEquals(0, meter.measureDeep(new WithAnnotatedInterface()));
260 | }
261 |
262 | @Test
263 | public void testUnmeteredAnnotationOnFieldParent() {
264 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
265 |
266 | long withoutSize = meter.measureDeep(new WithFieldWithAnnotatedParent(null));
267 |
268 | WithParentWithAnnotation field = new WithParentWithAnnotation("test");
269 | long withSize = meter.measureDeep(new WithFieldWithAnnotatedParent(field));
270 | assertEquals(withoutSize, withSize);
271 | }
272 |
273 | @Test
274 | public void testUnmeteredAnnotationOnFieldInterface() {
275 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
276 |
277 | long withoutSize = meter.measureDeep(new WithFieldAnnotatedInterface(null));
278 |
279 | AnnotatedInterface field = new AnnotatedInterface() {
280 | };
281 | long withSize = meter.measureDeep(new WithFieldAnnotatedInterface(field));
282 | assertEquals(withoutSize, withSize);
283 | }
284 |
285 | @Test
286 | public void testUnmeteredAnnotationOnFieldImplementedInterface() {
287 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
288 |
289 | long withoutSize = meter.measureDeep(new WithFieldTypeWithAnnotatedInterface(null));
290 |
291 | WithAnnotatedInterface field = new WithAnnotatedInterface();
292 | long withSize = meter.measureDeep(new WithFieldTypeWithAnnotatedInterface(field));
293 | assertEquals(withoutSize, withSize);
294 | }
295 |
296 | @SuppressWarnings("unused")
297 | private static class WithoutAnnotationField {
298 | private String s;
299 |
300 | public WithoutAnnotationField(String s) {
301 | this.s = s;
302 | }
303 | }
304 |
305 | private static class WithAnnotationField {
306 |
307 | @Unmetered
308 | private String s;
309 |
310 | public WithAnnotationField(String s) {
311 | this.s = s;
312 | }
313 | }
314 |
315 | @Unmetered
316 | @SuppressWarnings("unused")
317 | private static class ClassWithAnnotation {
318 | private String s;
319 |
320 | public ClassWithAnnotation(String s) {
321 | this.s = s;
322 | }
323 | }
324 |
325 | private static class WithParentWithAnnotation extends ClassWithAnnotation {
326 |
327 | public WithParentWithAnnotation(String s) {
328 | super(s);
329 | }
330 | }
331 |
332 | @SuppressWarnings("unused")
333 | private static class WithFieldWithAnnotatedParent {
334 |
335 | private final WithParentWithAnnotation field;
336 |
337 | public WithFieldWithAnnotatedParent(WithParentWithAnnotation field) {
338 | this.field = field;
339 | }
340 | }
341 |
342 | @Unmetered
343 | private interface AnnotatedInterface {
344 |
345 | }
346 |
347 | @SuppressWarnings("unused")
348 | private static class WithFieldAnnotatedInterface {
349 |
350 | private final AnnotatedInterface field;
351 |
352 | public WithFieldAnnotatedInterface(AnnotatedInterface field) {
353 | this.field = field;
354 | }
355 | }
356 |
357 | private static class WithAnnotatedInterface implements AnnotatedInterface {
358 |
359 | }
360 |
361 | @SuppressWarnings("unused")
362 | private static class WithFieldTypeWithAnnotatedInterface {
363 |
364 | private final WithAnnotatedInterface field;
365 |
366 | public WithFieldTypeWithAnnotatedInterface(WithAnnotatedInterface field) {
367 | this.field = field;
368 | }
369 | }
370 |
371 |
372 | @Test
373 | public void testMeasureWithLambdaField() {
374 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
375 |
376 | meter.measureDeep(new WithLambdaField("test"));
377 | }
378 |
379 | private static class WithLambdaField {
380 |
381 | private final String field;
382 |
383 | @SuppressWarnings("unused")
384 | private final Predicate greater;
385 |
386 | public WithLambdaField(String field)
387 | {
388 | this.field = field;
389 | this.greater = (e) -> WithLambdaField.this.isGreater(e);
390 | }
391 |
392 | public boolean isGreater(String s)
393 | {
394 | return s.compareTo(field) > 0;
395 | }
396 | }
397 |
398 | @Test
399 | public void testMeasureDeepWithPublicFieldInPackagePrivate() {
400 |
401 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
402 | PublicClassWithPackageProtectedClassField object = new PublicClassWithPackageProtectedClassField("publicField", "packageProtected", "protected", "private");
403 | long expected = object.measureDeep(meter);
404 | assertEquals(expected, meter.measureDeep(object));
405 | }
406 |
407 | @Test
408 | public void testMeasurableInstance() {
409 | MemoryMeter meter = MemoryMeter.builder().withGuessing(guess).build();
410 |
411 | ChildMeasurable bart = new ChildMeasurable("Bart");
412 | ChildMeasurable lisa = new ChildMeasurable("Lisa");
413 | ChildMeasurable maggie = new ChildMeasurable("Maggie");
414 | ParentMeasurable homer = new ParentMeasurable("Homer", bart, lisa, maggie);
415 |
416 | long expectedSize = meter.measure(homer)
417 | + meter.measureDeep("Homer")
418 | + meter.measureArray(new Object[3])
419 | + meter.measure(bart)
420 | + meter.measureDeep("Bart")
421 | + meter.measure(lisa)
422 | + meter.measureDeep("Lisa")
423 | + meter.measure(maggie)
424 | + meter.measureDeep("Maggie");
425 |
426 | assertEquals(expectedSize, meter.measureDeep(homer));
427 | Assert.assertTrue("the addChildrenTo method has not been called", homer.checkUsage());
428 | }
429 |
430 | private static class ParentMeasurable implements Measurable
431 | {
432 | private final String name;
433 |
434 | private final ChildMeasurable[] children;
435 |
436 | boolean hasAddChildrenBeenUsed;
437 |
438 | public ParentMeasurable(String name, ChildMeasurable... children) {
439 | this.name = name;
440 | this.children = children;
441 | }
442 |
443 | @Override
444 | public void addChildrenTo(MeasurementStack stack) {
445 | hasAddChildrenBeenUsed = true;
446 |
447 | stack.pushObject(this, "name", name);
448 | stack.pushObject(this, "children", children);
449 | }
450 |
451 | public boolean checkUsage() {
452 |
453 | boolean check = hasAddChildrenBeenUsed;
454 | for (ChildMeasurable child : children)
455 | check &= child.hasAddChildrenBeenUsed;
456 | return check;
457 | }
458 | }
459 |
460 | private static class ChildMeasurable implements Measurable
461 | {
462 | private String name;
463 | boolean hasAddChildrenBeenUsed;
464 |
465 | public ChildMeasurable(String name) {
466 | this.name = name;
467 | }
468 |
469 | @Override
470 | public void addChildrenTo(MeasurementStack stack) {
471 | hasAddChildrenBeenUsed = true;
472 |
473 | stack.pushObject(this, "name", name);
474 | }
475 | }
476 | }
477 |
--------------------------------------------------------------------------------
/test/org/github/jamm/jmh/BenchmarkMeasureArray.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.jmh;
2 |
3 | import java.lang.reflect.Array;
4 | import java.util.Random;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import org.github.jamm.MemoryMeter;
8 | import org.openjdk.jmh.annotations.Benchmark;
9 | import org.openjdk.jmh.annotations.BenchmarkMode;
10 | import org.openjdk.jmh.annotations.Fork;
11 | import org.openjdk.jmh.annotations.Level;
12 | import org.openjdk.jmh.annotations.Measurement;
13 | import org.openjdk.jmh.annotations.Mode;
14 | import org.openjdk.jmh.annotations.OutputTimeUnit;
15 | import org.openjdk.jmh.annotations.Param;
16 | import org.openjdk.jmh.annotations.Scope;
17 | import org.openjdk.jmh.annotations.Setup;
18 | import org.openjdk.jmh.annotations.State;
19 | import org.openjdk.jmh.annotations.Threads;
20 | import org.openjdk.jmh.annotations.Warmup;
21 | import org.openjdk.jmh.infra.Blackhole;
22 |
23 | @Threads(3)
24 | @Fork(value = 1, jvmArgsPrepend = {
25 | "-javaagent:target/jamm-0.4.1-SNAPSHOT.jar",
26 | })
27 | @Warmup(iterations=4, time=5)
28 | @Measurement(iterations=5, time=5)
29 | @BenchmarkMode(Mode.AverageTime)
30 | @State(Scope.Thread)
31 | @OutputTimeUnit(TimeUnit.MICROSECONDS)
32 | public class BenchmarkMeasureArray {
33 |
34 | @Param({"INSTRUMENTATION", "INSTRUMENTATION_AND_SPECIFICATION", "SPECIFICATION", "UNSAFE"})
35 | private String guess;
36 |
37 | private MemoryMeter meter;
38 |
39 | private static Object[] arrays;
40 | private static Object[] byteArrays;
41 |
42 | static {
43 | try {
44 |
45 | Class>[] choices = new Class>[] {byte.class, int.class, double.class, Object.class};
46 |
47 | Random random = new Random();
48 |
49 | arrays = new Object[1000];
50 | for (int i = 0; i < arrays.length; i++) {
51 | arrays[i] = Array.newInstance(choices[random.nextInt(choices.length)], random.nextInt(100));
52 | }
53 |
54 | byteArrays = new Object[1000];
55 | for (int i = 0; i < byteArrays.length; i++) {
56 | byteArrays[i] = new byte[random.nextInt(100)];
57 | }
58 |
59 | } catch (Exception e) {
60 | e.printStackTrace();
61 | }
62 | }
63 |
64 | @Setup(Level.Iteration)
65 | public void setup() throws Exception {
66 | MemoryMeter.Guess guess = MemoryMeter.Guess.valueOf(this.guess);
67 | this.meter = MemoryMeter.builder().withGuessing(guess).build();
68 | }
69 |
70 | @Benchmark
71 | public void measure(Blackhole bh) {
72 | for (Object o : arrays)
73 | bh.consume(meter.measure(o));
74 | }
75 |
76 | @Benchmark
77 | public void measureByteArray(Blackhole bh) {
78 | for (Object o : byteArrays)
79 | bh.consume(meter.measureArray((byte[]) o));
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/test/org/github/jamm/jmh/BenchmarkMeasureInstance.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.jmh;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | import org.github.jamm.MemoryMeter;
7 | import org.openjdk.jmh.annotations.Benchmark;
8 | import org.openjdk.jmh.annotations.BenchmarkMode;
9 | import org.openjdk.jmh.annotations.Fork;
10 | import org.openjdk.jmh.annotations.Level;
11 | import org.openjdk.jmh.annotations.Measurement;
12 | import org.openjdk.jmh.annotations.Mode;
13 | import org.openjdk.jmh.annotations.OutputTimeUnit;
14 | import org.openjdk.jmh.annotations.Param;
15 | import org.openjdk.jmh.annotations.Scope;
16 | import org.openjdk.jmh.annotations.Setup;
17 | import org.openjdk.jmh.annotations.State;
18 | import org.openjdk.jmh.annotations.Threads;
19 | import org.openjdk.jmh.annotations.Warmup;
20 | import org.openjdk.jmh.infra.Blackhole;
21 |
22 | @Threads(3)
23 | @Fork(value = 1, jvmArgsPrepend = {
24 | "-javaagent:target/jamm-0.4.1-SNAPSHOT.jar",
25 | })
26 | @Warmup(iterations=4, time=5)
27 | @Measurement(iterations=5, time=5)
28 | @BenchmarkMode(Mode.AverageTime)
29 | @State(Scope.Thread)
30 | @OutputTimeUnit(TimeUnit.MICROSECONDS)
31 | public class BenchmarkMeasureInstance {
32 |
33 | @Param({"INSTRUMENTATION", "INSTRUMENTATION_AND_SPECIFICATION", "SPECIFICATION", "UNSAFE"})
34 | private String guess;
35 |
36 | private MemoryMeter meter;
37 |
38 | private static Object[] objects;
39 |
40 | static
41 | {
42 | try {
43 | Class>[] choices = new Class>[] {ClassWithoutFields.class,
44 | ClassWithOnePrimitiveFields.class,
45 | ClassWithOneObjectField.class,
46 | ClassWithTreeObjectFields.class,
47 | ClassWithOneObjectFieldAndTwoPrimitives.class,
48 | ClassWithFiveObjectFields.class};
49 |
50 | Random random = new Random();
51 |
52 | objects = new Object[1000];
53 | for (int i = 0; i < objects.length; i++) {
54 | objects[i] = choices[random.nextInt(choices.length)].newInstance();
55 | }
56 | } catch (Exception e) {
57 | e.printStackTrace();
58 | }
59 | }
60 |
61 | @Setup(Level.Iteration)
62 | public void setup() throws Exception {
63 | MemoryMeter.Guess guess = MemoryMeter.Guess.valueOf(this.guess);
64 | this.meter = MemoryMeter.builder().withGuessing(guess).build();
65 | }
66 |
67 | @Benchmark
68 | public void measure(Blackhole bh) {
69 | for (Object o : objects)
70 | bh.consume(meter.measure(o));
71 | }
72 |
73 | public static class ClassWithoutFields {
74 | }
75 |
76 | public static class ClassWithOnePrimitiveFields {
77 | int intField;
78 | }
79 |
80 | public static class ClassWithOneObjectField {
81 | Object field = new Object();
82 | }
83 |
84 | public static class ClassWithTreeObjectFields {
85 | Object first = new Object();
86 |
87 | Object second = new Object();
88 |
89 | String[] third = new String[] {"one", "two"};
90 | }
91 |
92 | public static class ClassWithOneObjectFieldAndTwoPrimitives {
93 | byte first;
94 |
95 | Object second = new Object();
96 |
97 | double third;
98 | }
99 |
100 | public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields {
101 | Object fourth = new Object();
102 |
103 | int[] fifth = new int[12];
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/test/org/github/jamm/jmh/BenchmarkMeasureString.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.jmh;
2 |
3 | import java.util.Random;
4 | import java.util.concurrent.TimeUnit;
5 |
6 | import org.github.jamm.MemoryMeter;
7 | //import org.github.jamm.MemoryMeterStrategy;
8 | //import org.github.jamm.utils.ArrayMeasurementUtils;
9 | import org.openjdk.jmh.annotations.Benchmark;
10 | import org.openjdk.jmh.annotations.BenchmarkMode;
11 | import org.openjdk.jmh.annotations.Fork;
12 | import org.openjdk.jmh.annotations.Level;
13 | import org.openjdk.jmh.annotations.Measurement;
14 | import org.openjdk.jmh.annotations.Mode;
15 | import org.openjdk.jmh.annotations.OutputTimeUnit;
16 | import org.openjdk.jmh.annotations.Param;
17 | import org.openjdk.jmh.annotations.Scope;
18 | import org.openjdk.jmh.annotations.Setup;
19 | import org.openjdk.jmh.annotations.State;
20 | import org.openjdk.jmh.annotations.Threads;
21 | import org.openjdk.jmh.annotations.Warmup;
22 | import org.openjdk.jmh.infra.Blackhole;
23 |
24 | @Threads(3)
25 | @Fork(value = 1, jvmArgsPrepend = {
26 | "-javaagent:target/jamm-0.4.1-SNAPSHOT.jar",
27 | // "--add-opens=java.base/java.lang=ALL-UNNAMED"
28 | })
29 | @Warmup(iterations=4, time=5)
30 | @Measurement(iterations=5, time=5)
31 | @BenchmarkMode(Mode.AverageTime)
32 | @State(Scope.Thread)
33 | @OutputTimeUnit(TimeUnit.MICROSECONDS)
34 | public class BenchmarkMeasureString
35 | {
36 | @Param({"INSTRUMENTATION", "INSTRUMENTATION_AND_SPECIFICATION", "UNSAFE", "SPECIFICATION"})
37 | private String guess;
38 |
39 | private MemoryMeter meter;
40 |
41 | private static String[] strings;
42 |
43 | private long emptySize;
44 |
45 | static
46 | {
47 | try {
48 | Random random = new Random();
49 |
50 | String[] array = new String[1000];
51 | for (int i = 0; i < array.length; i++) {
52 | int length = random.nextInt(50);
53 | array[i] = random.ints('a', 'z' + 1)
54 | .limit(length)
55 | .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
56 | .toString();
57 | }
58 | strings = array;
59 |
60 | } catch (Exception e) {
61 | e.printStackTrace();
62 | }
63 | }
64 |
65 | @Setup(Level.Iteration)
66 | public void setup() throws Exception {
67 | MemoryMeter.Guess guess = MemoryMeter.Guess.valueOf(this.guess);
68 | this.meter = MemoryMeter.builder().withGuessing(guess).build();
69 | emptySize = this.meter.measure("");
70 | }
71 |
72 | @Benchmark
73 | public void measureStringDeep(Blackhole bh) {
74 | for (String s : strings)
75 | bh.consume(meter.measureStringDeep(s));
76 | }
77 | //
78 | // @Benchmark
79 | // public void measureReference(Blackhole bh) {
80 | // for (String s : strings) {
81 | // bh.consume(emptySize + ArrayMeasurementUtils.computeArraySize(MemoryMeterStrategy.MEMORY_LAYOUT.getArrayHeaderSize(), s.length(), Character.BYTES, MemoryMeterStrategy.MEMORY_LAYOUT.getObjectAlignment()));
82 | // }
83 | // }
84 |
85 | @Benchmark
86 | public void measureDeep(Blackhole bh) {
87 | for (String s : strings)
88 | bh.consume(meter.measureDeep(s));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/test/org/github/jamm/jmh/BenchmarkObjectGraphTraversal.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.jmh;
2 |
3 | import java.util.concurrent.TimeUnit;
4 |
5 | import org.github.jamm.FieldAndClassFilter;
6 | import org.github.jamm.FieldFilter;
7 | import org.github.jamm.Filters;
8 | import org.github.jamm.Measurable;
9 | import org.github.jamm.MeasurementStack;
10 | import org.github.jamm.MemoryMeter;
11 | import org.github.jamm.MemoryMeterStrategy;
12 | import org.github.jamm.listeners.NoopMemoryMeterListener;
13 | import org.openjdk.jmh.annotations.Benchmark;
14 | import org.openjdk.jmh.annotations.BenchmarkMode;
15 | import org.openjdk.jmh.annotations.Fork;
16 | import org.openjdk.jmh.annotations.Level;
17 | import org.openjdk.jmh.annotations.Measurement;
18 | import org.openjdk.jmh.annotations.Mode;
19 | import org.openjdk.jmh.annotations.OutputTimeUnit;
20 | import org.openjdk.jmh.annotations.Scope;
21 | import org.openjdk.jmh.annotations.Setup;
22 | import org.openjdk.jmh.annotations.State;
23 | import org.openjdk.jmh.annotations.Threads;
24 | import org.openjdk.jmh.annotations.Warmup;
25 | import org.openjdk.jmh.infra.Blackhole;
26 |
27 | @Threads(3)
28 | @Fork(value = 1, jvmArgsPrepend = {
29 | "-javaagent:target/jamm-0.4.1-SNAPSHOT.jar",
30 | // "--add-opens=java.base/java.lang=ALL-UNNAMED"
31 | })
32 | @Warmup(iterations=4, time=5)
33 | @Measurement(iterations=5, time=5)
34 | @BenchmarkMode(Mode.AverageTime)
35 | @State(Scope.Thread)
36 | @OutputTimeUnit(TimeUnit.MICROSECONDS)
37 | public class BenchmarkObjectGraphTraversal
38 | {
39 | private MemoryMeter meter;
40 |
41 | /**
42 | * The object being measured through reflection
43 | */
44 | private static Object OBJ;
45 |
46 | /**
47 | * The object being measured through the Measurable interface
48 | */
49 | private static Object MEASURABLE;
50 |
51 | static {
52 |
53 | OBJ = new ClassWithFiveObjectFields(new byte[20],
54 | new ClassWithOneObjectFieldsAndTwoPrimitives((byte) 8, new ClassWithoutFields(), Double.MAX_VALUE),
55 | new ClassWithOneObjectField(new Object[] {"test", new ClassWithOneObjectField(new Object()), new int[3]}),
56 | new ClassWithTreeObjectFields(new ClassWithTreeObjectFields(Boolean.TRUE, new ClassWithOnePrimitiveFields(12), new ClassWithoutFields()),
57 | new ClassWithOneObjectField(new Object()),
58 | new ClassWithoutFields()),
59 | "end");
60 |
61 | MEASURABLE = new MeasurableClassWithFiveObjectFields(new byte[20],
62 | new MeasurableClassWithOneObjectFieldsAndTwoPrimitives((byte) 8, new MeasurableClassWithoutFields(), Double.MAX_VALUE),
63 | new MeasurableClassWithOneObjectField(new Object[] {"test", new MeasurableClassWithOneObjectField(new Object()), new int[3]}),
64 | new MeasurableClassWithTreeObjectFields(new MeasurableClassWithTreeObjectFields(Boolean.TRUE, new MeasurableClassWithOnePrimitiveFields(12), new MeasurableClassWithoutFields()),
65 | new MeasurableClassWithOneObjectField(new Object()),
66 | new MeasurableClassWithoutFields()),
67 | "end");
68 | }
69 |
70 | @Setup(Level.Iteration)
71 | public void setup() throws Exception {
72 | MemoryMeterStrategy strategy = o -> 1;
73 | FieldFilter fieldFilter = Filters.getFieldFilters(false, false, false);
74 | FieldAndClassFilter classFilter = Filters.getClassFilters(false);
75 |
76 | this.meter = new MemoryMeter(strategy, classFilter, fieldFilter, NoopMemoryMeterListener.FACTORY);
77 | }
78 |
79 | @Benchmark
80 | public void measureThroughReflection(Blackhole bh) {
81 | bh.consume(meter.measureDeep(OBJ));
82 | }
83 |
84 | @Benchmark
85 | public void measureThroughMeasurable(Blackhole bh) {
86 | bh.consume(meter.measureDeep(MEASURABLE));
87 | }
88 |
89 | public static class ClassWithoutFields {
90 |
91 | }
92 |
93 | public static class MeasurableClassWithoutFields implements Measurable {
94 |
95 | @Override
96 | public void addChildrenTo(MeasurementStack stack)
97 | {
98 | }
99 | }
100 |
101 | @SuppressWarnings("unused")
102 | public static class ClassWithOnePrimitiveFields {
103 |
104 | private int intField;
105 |
106 | public ClassWithOnePrimitiveFields(int intField) {
107 | this.intField = intField;
108 | }
109 | }
110 |
111 | @SuppressWarnings("unused")
112 | public static class MeasurableClassWithOnePrimitiveFields implements Measurable {
113 |
114 | private int intField;
115 |
116 | @Override
117 | public long shallowSize(MemoryMeterStrategy strategy) {
118 | return 1;
119 | }
120 |
121 | public MeasurableClassWithOnePrimitiveFields(int intField) {
122 | this.intField = intField;
123 | }
124 |
125 | @Override
126 | public void addChildrenTo(MeasurementStack stack) {
127 | }
128 | }
129 |
130 | @SuppressWarnings("unused")
131 | public static class ClassWithOneObjectField {
132 |
133 | public static String staticField = "static";
134 |
135 | private Object field;
136 |
137 | public ClassWithOneObjectField(Object field) {
138 | this.field = field;
139 | }
140 | }
141 |
142 | @SuppressWarnings("unused")
143 | public static class MeasurableClassWithOneObjectField implements Measurable {
144 |
145 | public static Object staticField = "static";
146 |
147 | private Object field;
148 |
149 | public MeasurableClassWithOneObjectField(Object field) {
150 | this.field = field;
151 | }
152 |
153 | @Override
154 | public long shallowSize(MemoryMeterStrategy strategy) {
155 | return 1;
156 | }
157 |
158 | @Override
159 | public void addChildrenTo(MeasurementStack stack) {
160 | stack.pushObject(this, "field", field);
161 | }
162 | }
163 |
164 | @SuppressWarnings("unused")
165 | public static class ClassWithTreeObjectFields {
166 |
167 | private Object first;
168 |
169 | private Object second;
170 |
171 | private Object third;
172 |
173 | public ClassWithTreeObjectFields(Object first, Object second, Object third) {
174 | this.first = first;
175 | this.second = second;
176 | this.third = third;
177 | }
178 | }
179 |
180 | @SuppressWarnings("unused")
181 | public static class MeasurableClassWithTreeObjectFields implements Measurable {
182 |
183 | private Object first;
184 |
185 | private Object second;
186 |
187 | private Object third;
188 |
189 | public MeasurableClassWithTreeObjectFields(Object first, Object second, Object third) {
190 | this.first = first;
191 | this.second = second;
192 | this.third = third;
193 | }
194 |
195 | @Override
196 | public long shallowSize(MemoryMeterStrategy strategy) {
197 | return 1;
198 | }
199 |
200 | @Override
201 | public void addChildrenTo(MeasurementStack stack) {
202 | stack.pushObject(this, "first", first);
203 | stack.pushObject(this, "second", second);
204 | stack.pushObject(this, "third", third);
205 | }
206 | }
207 |
208 | @SuppressWarnings("unused")
209 | public static class ClassWithOneObjectFieldsAndTwoPrimitives {
210 |
211 | private byte first;
212 |
213 | private Object second;
214 |
215 | private double third;
216 |
217 | public ClassWithOneObjectFieldsAndTwoPrimitives(byte first, Object second, double third) {
218 | this.first = first;
219 | this.second = second;
220 | this.third = third;
221 | }
222 | }
223 |
224 | @SuppressWarnings("unused")
225 | public static class MeasurableClassWithOneObjectFieldsAndTwoPrimitives implements Measurable {
226 |
227 | private byte first;
228 |
229 | private Object second;
230 |
231 | private double third;
232 |
233 | public MeasurableClassWithOneObjectFieldsAndTwoPrimitives(byte first, Object second, double third) {
234 | this.first = first;
235 | this.second = second;
236 | this.third = third;
237 | }
238 |
239 | @Override
240 | public long shallowSize(MemoryMeterStrategy strategy) {
241 | return 1;
242 | }
243 |
244 | @Override
245 | public void addChildrenTo(MeasurementStack stack) {
246 | stack.pushObject(this, "second", second);
247 | }
248 | }
249 |
250 | @SuppressWarnings("unused")
251 | public static class ClassWithFiveObjectFields extends ClassWithTreeObjectFields {
252 |
253 | private Object fourth;
254 |
255 | private Object fifth;
256 |
257 | public ClassWithFiveObjectFields(Object first, Object second, Object third, Object fourth, Object fifth) {
258 | super(first, second, third);
259 | this.fourth = fourth;
260 | this.fifth = fifth;
261 | }
262 | }
263 |
264 | @SuppressWarnings("unused")
265 | public static class MeasurableClassWithFiveObjectFields extends MeasurableClassWithTreeObjectFields {
266 |
267 | private Object fourth;
268 |
269 | private Object fifth;
270 |
271 | public MeasurableClassWithFiveObjectFields(Object first, Object second, Object third, Object fourth, Object fifth) {
272 | super(first, second, third);
273 | this.fourth = fourth;
274 | this.fifth = fifth;
275 | }
276 |
277 | @Override
278 | public long shallowSize(MemoryMeterStrategy strategy) {
279 | return 1;
280 | }
281 |
282 | @Override
283 | public void addChildrenTo(MeasurementStack stack) {
284 | super.addChildrenTo(stack);
285 | stack.pushObject(this, "fourth", fourth);
286 | stack.pushObject(this, "fifth", fifth);
287 | }
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/test/org/github/jamm/strategies/MemoryMeterStrategiesTest.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.strategies;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.List;
6 |
7 | import org.junit.BeforeClass;
8 | import org.junit.Test;
9 |
10 | import org.github.jamm.MemoryMeter.Guess;
11 |
12 | import static org.github.jamm.MemoryMeter.Guess.INSTRUMENTATION;
13 | import static org.github.jamm.MemoryMeter.Guess.INSTRUMENTATION_AND_SPECIFICATION;
14 | import static org.github.jamm.MemoryMeter.Guess.SPECIFICATION;
15 | import static org.github.jamm.MemoryMeter.Guess.UNSAFE;
16 | import static org.junit.Assert.*;
17 |
18 | public class MemoryMeterStrategiesTest {
19 |
20 | @BeforeClass
21 | public static void logInfoAtStartup() {
22 | System.setProperty("org.github.jamm.strategies.LogInfoAtStartup", "true");
23 | }
24 |
25 | @Test
26 | public void testInvalidEmptyGuessList() {
27 | MemoryMeterStrategies strategies = MemoryMeterStrategies.getInstance();
28 | assertTrue(strategies.hasInstrumentation());
29 | assertTrue(strategies.hasUnsafe());
30 |
31 | List guesses = new ArrayList<>();
32 | try {
33 | strategies.getStrategy(guesses);
34 | fail();
35 | } catch (IllegalArgumentException e) {
36 | assertEquals("The guessList argument is empty", e.getMessage());
37 | }
38 |
39 | guesses = Arrays.asList(INSTRUMENTATION, UNSAFE, SPECIFICATION);
40 | assertTrue(strategies.getStrategy(guesses) instanceof InstrumentationStrategy);
41 |
42 | guesses = Arrays.asList(INSTRUMENTATION_AND_SPECIFICATION, UNSAFE, SPECIFICATION);
43 | assertTrue(strategies.getStrategy(guesses) instanceof InstrumentationAndSpecStrategy);
44 |
45 | guesses = Arrays.asList(UNSAFE, INSTRUMENTATION);
46 | assertInvalidOrder(strategies, guesses);
47 |
48 | guesses = Arrays.asList(UNSAFE, INSTRUMENTATION_AND_SPECIFICATION);
49 | assertInvalidOrder(strategies, guesses);
50 |
51 | guesses = Arrays.asList(SPECIFICATION, INSTRUMENTATION);
52 | assertInvalidOrder(strategies, guesses);
53 |
54 | guesses = Arrays.asList(SPECIFICATION, INSTRUMENTATION_AND_SPECIFICATION);
55 | assertInvalidOrder(strategies, guesses);
56 |
57 | guesses = Arrays.asList(SPECIFICATION, UNSAFE);
58 | assertInvalidOrder(strategies, guesses);
59 |
60 | guesses = Arrays.asList(INSTRUMENTATION_AND_SPECIFICATION, INSTRUMENTATION);
61 | assertInvalidOrder(strategies, guesses);
62 | }
63 |
64 | private void assertInvalidOrder(MemoryMeterStrategies strategies, List guesses) {
65 | try {
66 | strategies.getStrategy(guesses);
67 | fail();
68 | } catch (IllegalArgumentException e) {
69 | assertTrue(true);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/test/org/github/jamm/string/StringMeterTest.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.string;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.List;
7 |
8 | import org.junit.BeforeClass;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.junit.runners.Parameterized;
12 |
13 | import org.github.jamm.MemoryMeter;
14 | import org.github.jamm.MemoryMeter.Guess;
15 | import org.github.jamm.MemoryMeterStrategy;
16 | import org.github.jamm.strategies.MemoryMeterStrategies;
17 |
18 | import static org.junit.Assert.assertEquals;
19 | import static org.junit.Assert.assertFalse;
20 |
21 | @RunWith(Parameterized.class)
22 | public class StringMeterTest {
23 |
24 | @BeforeClass
25 | public static void logInfoAtStartup() {
26 | System.setProperty("org.github.jamm.strategies.LogInfoAtStartup", "true");
27 | }
28 |
29 | @BeforeClass
30 | public static void turnOffStringOptimization() {
31 | System.setProperty("org.github.jamm.string.Optimize", "false");
32 | }
33 |
34 | @Parameterized.Parameters
35 | public static Collection guesses() {
36 |
37 | return Arrays.asList(Guess.INSTRUMENTATION,
38 | Guess.INSTRUMENTATION_AND_SPECIFICATION,
39 | Guess.UNSAFE,
40 | Guess.SPECIFICATION);
41 | }
42 |
43 | private final Guess guess;
44 |
45 | public StringMeterTest(Guess guess) {
46 | this.guess = guess;
47 | }
48 |
49 | @Test
50 | public void testMeasureDeepString() {
51 |
52 | String[] strings = new String[] {"",
53 | "a",
54 | "a bit longuer",
55 | "significantly longuer",
56 | "...... really ...... really .... really ... really .... longuer",
57 | "with a chinese character: 我"};
58 |
59 | // The optimization is disabled for the MemoryMeter but not for the StringMeter which does not take into account the ENABLED field.
60 | MemoryMeter reference = MemoryMeter.builder().withGuessing(MemoryMeter.Guess.INSTRUMENTATION).build();
61 | assertFalse(MemoryMeter.useStringOptimization());
62 | StringMeter stringMeter = StringMeter.newInstance();
63 | List guesses = new ArrayList<>();
64 | guesses.add(guess);
65 | MemoryMeterStrategy strategy = MemoryMeterStrategies.getInstance().getStrategy(guesses);
66 |
67 | for (String string : strings) {
68 | assertEquals(reference.measureDeep(string), stringMeter.measureDeep(strategy, string));
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/test/org/github/jamm/testedclasses/PackageProtectedClass.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.testedclasses;
2 |
3 | import org.github.jamm.MemoryMeter;
4 |
5 | /**
6 | * Package protected class used by tests
7 | *
8 | */
9 | class PackageProtectedClass
10 | {
11 | public String publicField;
12 |
13 | String packageProtectedField;
14 |
15 | protected String protectedField;
16 |
17 | private String privateField;
18 |
19 | public PackageProtectedClass(String publicField,
20 | String packageProtectedField,
21 | String protectedField,
22 | String privateField) {
23 |
24 | this.publicField = publicField;
25 | this.packageProtectedField = packageProtectedField;
26 | this.protectedField = protectedField;
27 | this.privateField = privateField;
28 | }
29 |
30 | public long measureDeep(MemoryMeter meter) {
31 | return meter.measure(this)
32 | + meter.measureDeep(publicField)
33 | + meter.measureDeep(packageProtectedField)
34 | + meter.measureDeep(protectedField)
35 | + meter.measureDeep(privateField);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/org/github/jamm/testedclasses/PublicClassWithPackageProtectedClassField.java:
--------------------------------------------------------------------------------
1 | package org.github.jamm.testedclasses;
2 |
3 | import org.github.jamm.MemoryMeter;
4 |
5 | public class PublicClassWithPackageProtectedClassField
6 | {
7 | public static String publicStaticField = "publicStaticField";
8 |
9 | @SuppressWarnings("unused")
10 | private static String privateStaticField = "privateStaticField";
11 |
12 | public String publicField;
13 |
14 | String packageProtectedField;
15 |
16 | protected String protectedField;
17 |
18 | private String privateField;
19 |
20 | private PackageProtectedClass packageProtectedClass;
21 |
22 | public PublicClassWithPackageProtectedClassField(String publicField,
23 | String packageProtectedField,
24 | String protectedField,
25 | String privateField) {
26 |
27 | this.publicField = publicField;
28 | this.packageProtectedField = packageProtectedField;
29 | this.protectedField = protectedField;
30 | this.privateField = privateField;
31 | this.packageProtectedClass = new PackageProtectedClass("packageProtectedClassPublicField",
32 | "packageProtectedClassPackageProtectedField",
33 | "packageProtectedClassProtectedField",
34 | "packageProtectedClassPrivateField");
35 | }
36 |
37 | public long measureDeep(MemoryMeter meter) {
38 | return meter.measure(this)
39 | + meter.measureDeep(publicField)
40 | + meter.measureDeep(packageProtectedField)
41 | + meter.measureDeep(protectedField)
42 | + meter.measureDeep(privateField)
43 | + packageProtectedClass.measureDeep(meter);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/toolchains.example.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | jdk
6 |
7 | 1.8
8 | Oracle Corporation
9 |
10 |
11 | /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home
12 |
13 |
14 |
15 | jdk
16 |
17 | 11
18 | Oracle Corporation
19 |
20 |
21 | /Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home
22 |
23 |
24 |
25 | jdk
26 |
27 | 17
28 | Oracle Corporation
29 |
30 |
31 | /Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------