├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
├── release
├── src
├── main
│ └── java
│ │ └── net
│ │ └── intelie
│ │ └── introspective
│ │ ├── BloomObjectSizer.java
│ │ ├── ObjectSizer.java
│ │ ├── ThreadResources.java
│ │ ├── hotspot
│ │ ├── Field.java
│ │ ├── JVM.java
│ │ ├── Symbols.java
│ │ ├── Type.java
│ │ └── VMThread.java
│ │ ├── reflect
│ │ ├── ArrayPeeler.java
│ │ ├── ConstantDummyPeeler.java
│ │ ├── FastFieldAccessor.java
│ │ ├── GenericPeeler.java
│ │ ├── JVMPrimitives.java
│ │ ├── ObjectPeeler.java
│ │ ├── ReferencePeeler.java
│ │ ├── ReflectionCache.java
│ │ └── StringFastPath.java
│ │ └── util
│ │ ├── BloomVisitedSet.java
│ │ ├── ExpiringVisitedSet.java
│ │ ├── IdentityVisitedSet.java
│ │ ├── Preconditions.java
│ │ ├── UnsafeGetter.java
│ │ └── VisitedSet.java
└── test
│ └── java
│ ├── net
│ └── intelie
│ │ └── introspective
│ │ ├── BloomObjectSizerTest.java
│ │ ├── ObjectSizerTest.java
│ │ ├── Playground.java
│ │ ├── ThreadResourcesTest.java
│ │ ├── hotspot
│ │ └── JVMTest.java
│ │ ├── reflect
│ │ ├── ArrayPeelerTest.java
│ │ ├── FastFieldAccessorTest.java
│ │ ├── JVMPrimitivesTest.java
│ │ ├── ObjectPeelerTest.java
│ │ ├── ReflectionCacheTest.java
│ │ ├── StringFastPathTest.java
│ │ └── TestSizeUtils.java
│ │ └── util
│ │ ├── BloomVisitedSetTest.java
│ │ ├── ExpiringVisitedSetTest.java
│ │ ├── IdentityVisitedSetTest.java
│ │ ├── PreconditionsTest.java
│ │ ├── SuppressForbidden.java
│ │ └── UnsafeGetterTest.java
│ └── org
│ └── openjdk
│ └── jol
│ └── vm
│ └── LightVM.java
└── test
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.ipr
3 | *.iws
4 | *.log
5 | *.pyc
6 | *.versionsBackup
7 | .ideas
8 | .idea
9 | .DS_Store
10 | /target
11 | /*/target
12 | .jhw-cache
13 | ldapdata
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
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 2016 Intelie
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introspective
2 |
3 | JVM Introspection Utilities
4 |
5 | Initially based on https://github.com/apangin/helfy.
6 |
7 | This library has two main features:
8 |
9 | - Inspect the total amount of allocated bytes for any thread, with an implementation orders of magnitude faster than ThreadMXBean;
10 | - Estimate recursively the total amount of bytes an object holds.
11 |
12 | Also, with the tools provided here, you can inspect pretty much any internal JVM object.
13 |
14 | We tested it on Linux, in OpenJDK versions 8 to 13, but it should work well in other systems.
15 | No guarantee, though.
16 |
17 | **Notes**
18 |
19 | * We had to use introspection because ThreadMXBean is just too slow for our needs.
20 | * The API may seem weird sometimes, but it is mostly because we try to avoid allocations at all costs
21 | and this means we have to reuse objects whenever possible.
22 |
23 | ## Usage
24 |
25 | Introspective is available through Maven Central repository, just add the following
26 | dependency to your `pom.xml` file:
27 |
28 | ```xml
29 |
30 | net.intelie.introspective
31 | introspective
32 | 0.8
33 |
34 | ```
35 |
36 | ### Inspecting total allocated bytes
37 |
38 | ```java
39 | ThreadResources.allocatedBytes(Thread.currentThread())
40 | ```
41 |
42 | ### Estimating object size recursively
43 |
44 | ```java
45 | Map test = new HashMap<>();
46 | test.put(111, Arrays.asList("aaa", 222));
47 | test.put(333.0, Collections.singletonMap("bbb", 444));
48 |
49 | ObjectSizer sizer = new ObjectSizer();
50 | sizer.resetTo(test);
51 |
52 | while (sizer.moveNext()) {
53 | System.out.println(sizer.bytes() + " bytes: " + sizer.path());
54 | System.out.println(" class: " + sizer.type());
55 | System.out.println(" value: " + sizer.current());
56 | }
57 | ```
58 |
59 | The output for the snippet above will be:
60 |
61 | ```
62 | 48 bytes:
63 | class: class java.util.HashMap
64 | value: {333.0={bbb=444}, 111=[aaa, 222]}
65 | 80 bytes: .table
66 | class: class [Ljava.util.HashMap$Node;
67 | value: [Ljava.util.HashMap$Node;@e6ea0c6
68 | 32 bytes: .table[4]
69 | class: class java.util.HashMap$Node
70 | value: 333.0={bbb=444}
71 | 24 bytes: .table[4].key
72 | class: class java.lang.Double
73 | value: 333.0
74 | 40 bytes: .table[4].value
75 | class: class java.util.Collections$SingletonMap
76 | value: {bbb=444}
77 | 48 bytes: .table[4].value.k
78 | class: class java.lang.String
79 | value: bbb
80 | 16 bytes: .table[4].value.v
81 | class: class java.lang.Integer
82 | value: 444
83 | 16 bytes: .table[4].value.entrySet
84 | class: class java.util.Collections$SingletonSet
85 | value: [bbb=444]
86 | 24 bytes: .table[4].value.entrySet.element
87 | class: class java.util.AbstractMap$SimpleImmutableEntry
88 | value: bbb=444
89 | 32 bytes: .table[15]
90 | class: class java.util.HashMap$Node
91 | value: 111=[aaa, 222]
92 | 16 bytes: .table[15].key
93 | class: class java.lang.Integer
94 | value: 111
95 | 24 bytes: .table[15].value
96 | class: class java.util.Arrays$ArrayList
97 | value: [aaa, 222]
98 | 24 bytes: .table[15].value.a
99 | class: class [Ljava.io.Serializable;
100 | value: [Ljava.io.Serializable;@57fa26b7
101 | 48 bytes: .table[15].value.a[0]
102 | class: class java.lang.String
103 | value: aaa
104 | 16 bytes: .table[15].value.a[1]
105 | class: class java.lang.Integer
106 | value: 222
107 | 16 bytes: .entrySet
108 | class: class java.util.HashMap$EntrySet
109 | value: [333.0={bbb=444}, 111=[aaa, 222]]
110 | ```
111 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | net.intelie.introspective
8 | introspective
9 | 0.13-SNAPSHOT
10 |
11 | Introspective
12 | JVM Introspection Utilities
13 | http://www.intelie.com/introspective
14 |
15 |
16 | UTF-8
17 | 1.8
18 | 1.8
19 |
20 |
21 |
22 |
23 |
24 | The Apache License, Version 2.0
25 | http://www.apache.org/licenses/LICENSE-2.0.txt
26 |
27 |
28 |
29 |
30 |
31 | Juan Lopes
32 | juan.lopes@intelie.com.br
33 | Intelie
34 | http://www.intelie.com
35 |
36 |
37 |
38 |
39 | scm:git:git://github.com/intelie/introspective.git
40 | scm:git:ssh://github.com:intelie/introspective.git
41 | http://github.com/intelie/introspective/tree/master
42 |
43 |
44 |
45 |
46 |
47 | junit
48 | junit
49 | 4.13.2
50 | test
51 |
52 |
53 |
54 | org.assertj
55 | assertj-core
56 | 3.25.3
57 | test
58 |
59 |
60 |
61 | org.openjdk.jol
62 | jol-core
63 | 0.17
64 | test
65 |
66 |
67 |
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-enforcer-plugin
73 | 3.4.1
74 |
75 |
76 | enforce-maven
77 |
78 | enforce
79 |
80 |
81 |
82 |
83 | 3.2.5
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | de.thetaphi
93 | forbiddenapis
94 | 3.6
95 |
96 |
97 | jdk-unsafe
98 | jdk-deprecated
99 | jdk-system-out
100 |
101 |
102 | net.intelie.introspective.util.SuppressForbidden
103 |
104 |
105 |
106 |
107 |
108 | check
109 | testCheck
110 |
111 |
112 |
113 |
114 |
115 |
116 | org.apache.maven.plugins
117 | maven-compiler-plugin
118 | 3.12.1
119 |
120 |
121 |
122 | org.apache.maven.plugins
123 | maven-surefire-plugin
124 | 3.2.5
125 |
126 | false
127 |
128 |
129 |
130 |
131 | maven-clean-plugin
132 | 3.3.2
133 |
134 |
135 |
136 | maven-deploy-plugin
137 | 3.1.1
138 |
139 |
140 |
141 | maven-install-plugin
142 | 3.1.1
143 |
144 |
145 |
146 | maven-jar-plugin
147 | 3.3.0
148 |
149 |
150 |
151 | maven-resources-plugin
152 | 3.3.1
153 |
154 |
155 |
156 | maven-site-plugin
157 | 4.0.0-M13
158 |
159 |
160 |
161 |
162 |
163 |
164 | add-opens
165 |
166 | (1.8,)
167 |
168 |
169 |
170 |
171 | org.apache.maven.plugins
172 | maven-surefire-plugin
173 | 3.2.5
174 |
175 | false
176 | --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED -Djol.magicFieldOffset=true
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | release
185 |
186 |
187 |
188 | ossrh
189 | https://oss.sonatype.org/content/repositories/snapshots
190 |
191 |
192 |
193 |
194 |
195 |
196 | org.apache.maven.plugins
197 | maven-source-plugin
198 | 3.3.0
199 |
200 |
201 | attach-sources
202 |
203 | jar-no-fork
204 |
205 |
206 |
207 |
208 |
209 |
210 | org.apache.maven.plugins
211 | maven-javadoc-plugin
212 | 3.6.3
213 |
214 | none
215 |
216 |
217 |
218 | attach-javadocs
219 |
220 | jar
221 |
222 |
223 |
224 |
225 |
226 |
227 | org.apache.maven.plugins
228 | maven-gpg-plugin
229 | 3.1.0
230 |
231 |
232 | sign-artifacts
233 | verify
234 |
235 | sign
236 |
237 |
238 |
239 |
240 |
241 |
242 | org.sonatype.plugins
243 | nexus-staging-maven-plugin
244 | 1.6.13
245 | true
246 |
247 | ossrh
248 | https://oss.sonatype.org/
249 | true
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
--------------------------------------------------------------------------------
/release:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 |
5 | if [ $# -lt 2 ]; then
6 | echo "$0 "
7 | echo "$0 0.1 0.2"
8 | exit 0
9 | fi
10 |
11 | mvn versions:set -DnewVersion=$1
12 | mvn -P release clean deploy -Dgpg.executable=gpg2
13 |
14 | #prune old tags
15 | git fetch origin --prune +refs/tags/*:refs/tags/*
16 |
17 | git commit -am "Release version $1"
18 | git tag $1
19 | mvn versions:set -DnewVersion=$2-SNAPSHOT
20 | git commit -am "Developing version: $2"
21 |
22 | git push --tags origin HEAD
23 |
24 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/BloomObjectSizer.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.reflect.GenericPeeler;
4 | import net.intelie.introspective.reflect.JVMPrimitives;
5 | import net.intelie.introspective.reflect.ReflectionCache;
6 | import net.intelie.introspective.util.BloomVisitedSet;
7 | import net.intelie.introspective.util.VisitedSet;
8 |
9 | import java.util.ArrayDeque;
10 |
11 | public class BloomObjectSizer {
12 | private final VisitedSet seen;
13 | private final int maxWidth;
14 | private final ObjectSizer dfs;
15 | private final GenericPeeler peeler;
16 | private final ArrayDeque queue;
17 | private long count = 0;
18 | private long bytes = 0;
19 |
20 | public BloomObjectSizer(ReflectionCache cache, int m, int maxWidth, int maxDepth) {
21 | this.seen = new BloomVisitedSet(m, 3); //unscientific experiments suggest this is a good K
22 | this.maxWidth = maxWidth;
23 | this.dfs = new ObjectSizer(cache, seen, maxDepth);
24 | this.peeler = new GenericPeeler(cache);
25 | this.queue = new ArrayDeque<>(maxWidth);
26 | }
27 |
28 | public void softClear() {
29 | seen.softClear();
30 | count = 0;
31 | bytes = 0;
32 | }
33 |
34 | public void clear() {
35 | seen.clear();
36 | peeler.clear();
37 | dfs.clear();
38 | count = 0;
39 | bytes = 0;
40 | }
41 |
42 | public long skipped() {
43 | return dfs.skipped();
44 | }
45 |
46 | public long count() {
47 | return count;
48 | }
49 |
50 | public long bytes() {
51 | return bytes;
52 | }
53 |
54 | public void visit(Object obj) {
55 | ArrayDeque queue = this.queue;
56 | GenericPeeler peeler = this.peeler;
57 | VisitedSet seen = this.seen;
58 | ObjectSizer dfs = this.dfs;
59 | int maxWidth = this.maxWidth;
60 | long count = 0;
61 | long bytes = 0;
62 |
63 |
64 | if (obj != null)
65 | queue.add(obj);
66 |
67 |
68 | //using a BFS first to give objects higher in the tree a higher chance
69 | //of not being pruned
70 | while (!queue.isEmpty()) {
71 | count++;
72 | Object currentObj = queue.pollFirst();
73 | Class> currentType = currentObj.getClass();
74 |
75 | //the value is a boxed primitive
76 | long fast = JVMPrimitives.getFastPath(currentType, currentObj);
77 | if (fast >= 0) {
78 | bytes += JVMPrimitives.align(fast);
79 | continue;
80 | }
81 |
82 | bytes += JVMPrimitives.align(peeler.resetTo(currentType, currentObj));
83 | while (peeler.moveNext()) {
84 | Object next = peeler.current();
85 |
86 | if (queue.size() < maxWidth) {
87 | if (seen.enter(next) < 0) continue;
88 | queue.add(next);
89 | } else {
90 | //fallback to DFS
91 | dfs.set(next);
92 | while (dfs.moveNext()) {
93 | count++;
94 | bytes += dfs.bytes();
95 | }
96 | }
97 | }
98 | }
99 | this.count = count;
100 | this.bytes = bytes;
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/ObjectSizer.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.reflect.*;
4 | import net.intelie.introspective.util.IdentityVisitedSet;
5 | import net.intelie.introspective.util.VisitedSet;
6 |
7 | public class ObjectSizer {
8 | private final StringBuilder builder = new StringBuilder();
9 | private final ReflectionCache cache;
10 | private final VisitedSet seen;
11 | private final ReferencePeeler[] stack;
12 | private final int[] stackExit;
13 | private final int maxDepth;
14 | private int index = 0;
15 | private Object current;
16 | private ReferencePeeler currentPeeler;
17 | private boolean hasNextPeeler = false;
18 | private long bytes;
19 | private long skipped;
20 | private Class> type;
21 |
22 | public ObjectSizer() {
23 | this(new ReflectionCache(), new IdentityVisitedSet(), 1 << 15);
24 | }
25 |
26 | public ObjectSizer(ReflectionCache cache, VisitedSet seen, int maxDepth) {
27 | this.cache = cache;
28 | this.seen = seen;
29 | this.stack = new ReferencePeeler[maxDepth];
30 | this.stackExit = new int[maxDepth];
31 | this.maxDepth = maxDepth;
32 | stack[0] = new ConstantDummyPeeler();
33 | for (int i = 1; i < stack.length; i++) {
34 | stack[i] = new GenericPeeler(cache);
35 | }
36 | }
37 |
38 | public void clear() {
39 | current = null;
40 | type = null;
41 | bytes = 0;
42 | skipped = 0;
43 | seen.clear();
44 |
45 | for (ReferencePeeler peelr : stack)
46 | peelr.clear();
47 |
48 | index = -1;
49 | currentPeeler = null;
50 | hasNextPeeler = false;
51 | }
52 |
53 | public void resetTo(Object obj) {
54 | skipped = 0;
55 | seen.softClear();
56 | set(obj);
57 | }
58 |
59 | public void set(Object obj) {
60 | index = -1;
61 | currentPeeler = null;
62 | hasNextPeeler = true;
63 | stack[0].resetTo(null, obj);
64 | }
65 |
66 | public long skipped() {
67 | return skipped;
68 | }
69 |
70 | public boolean skipChildren() {
71 | if (!hasNextPeeler)
72 | return false;
73 |
74 | seen.exit(currentPeeler.current(), stackExit[index]);
75 | hasNextPeeler = false;
76 | skipped++;
77 | return true;
78 | }
79 |
80 | public boolean moveNext() {
81 | VisitedSet seen = this.seen;
82 | ReferencePeeler[] stack = this.stack;
83 | int index = this.index;
84 | ReferencePeeler peeler = hasNextPeeler ? stack[++index] : currentPeeler;
85 |
86 | while (true) {
87 | if (peeler.moveNext()) {
88 | Object currentObj = peeler.current();
89 | int enterIndex = seen.enter(currentObj);
90 | if (enterIndex < 0)
91 | continue;
92 |
93 | this.currentPeeler = peeler;
94 | this.current = currentObj;
95 | this.index = index;
96 | Class> currentType = this.type = currentObj.getClass();
97 |
98 | //fast path: the value is a boxed primitive
99 | long fast = JVMPrimitives.getFastPath(currentType, currentObj);
100 | if (fast >= 0) {
101 | this.bytes = fast;
102 | this.hasNextPeeler = false;
103 | seen.exit(currentObj, enterIndex);
104 | return true;
105 | }
106 |
107 | bytes = stack[index + 1].resetTo(currentType, currentObj);
108 | if (index + 2 < maxDepth) {
109 | stackExit[index] = enterIndex;
110 | hasNextPeeler = true;
111 | } else {
112 | seen.exit(currentObj, enterIndex);
113 | hasNextPeeler = false;
114 | skipped++;
115 | }
116 | return true;
117 | } else {
118 | if (--index < 0) return false;
119 | peeler = stack[index];
120 | seen.exit(peeler.current(), stackExit[index]);
121 | }
122 | }
123 | }
124 |
125 | public int visitDepth() {
126 | return index;
127 | }
128 |
129 | public Object current() {
130 | return current;
131 | }
132 |
133 | public Class> type() {
134 | return type;
135 | }
136 |
137 | public long bytes() {
138 | return JVMPrimitives.align(bytes);
139 | }
140 |
141 | public long unalignedBytes() {
142 | return bytes;
143 | }
144 |
145 | public Object pathSegment(int index) {
146 | return stack[index + 1].currentIndex();
147 | }
148 |
149 | public String path() {
150 | builder.setLength(0);
151 | for (int i = 0; i < index; i++) {
152 | Object index = pathSegment(i);
153 | if (index instanceof String) {
154 | builder.append('.').append(index);
155 | } else {
156 | builder.append('[').append(index).append(']');
157 | }
158 | }
159 | return builder.toString();
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/ThreadResources.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.hotspot.Field;
4 | import net.intelie.introspective.hotspot.JVM;
5 | import net.intelie.introspective.hotspot.Type;
6 | import net.intelie.introspective.hotspot.VMThread;
7 |
8 | public abstract class ThreadResources {
9 | private static final JVM vm;
10 | private static final Type threadType;
11 | private static final Type tlabType;
12 | private static final Field allocatedBytes;
13 | private static final Field tlab;
14 | private static final Field tlabTop;
15 | private static final Field tlabStart;
16 | private static final boolean validAllocated, validTlab;
17 |
18 | static {
19 | vm = initJVM();
20 | threadType = initType("Thread");
21 | allocatedBytes = initField(threadType, "_allocated_bytes");
22 | validAllocated = allocatedBytes != null;
23 |
24 | tlabType = initType("ThreadLocalAllocBuffer");
25 | tlab = initField(threadType, "_tlab");
26 | tlabStart = initField(tlabType, "_start");
27 | tlabTop = initField(tlabType, "_top");
28 | validTlab = tlab != null && tlabStart != null && tlabTop != null;
29 | }
30 |
31 | private static Field initField(Type type, String name) {
32 | return type != null ? type.field(name) : null;
33 | }
34 |
35 | private static Type initType(String name) {
36 | return vm != null ? vm.type(name) : null;
37 | }
38 |
39 | private static JVM initJVM() {
40 | try {
41 | return new JVM();
42 | } catch (Throwable e) {
43 | return null;
44 | }
45 | }
46 |
47 |
48 | public static boolean isValidAllocated() {
49 | return validAllocated;
50 | }
51 |
52 | public static boolean isValidTlab() {
53 | return validTlab;
54 | }
55 |
56 |
57 | public static long allocatedBytes() {
58 | return allocatedBytes(Thread.currentThread());
59 | }
60 |
61 | public static long allocatedBytes(Thread thread) {
62 | if (!validAllocated) return 0;
63 | long offset = VMThread.of(thread);
64 | long allocated = vm.getLong(offset + allocatedBytes.offset);
65 | if (!validTlab) return allocated;
66 |
67 | long top = vm.getAddress(offset + tlab.offset + tlabTop.offset);
68 | long start = vm.getAddress(offset + tlab.offset + tlabStart.offset);
69 | return allocated + (top - start);
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/hotspot/Field.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | public class Field implements Comparable {
4 | public final String name;
5 | public final String typeName;
6 | public final long offset;
7 | public final boolean isStatic;
8 |
9 | Field(String name, String typeName, long offset, boolean isStatic) {
10 | this.name = name;
11 | this.typeName = typeName;
12 | this.offset = offset;
13 | this.isStatic = isStatic;
14 | }
15 |
16 | @Override
17 | public int compareTo(Field o) {
18 | if (isStatic != o.isStatic) {
19 | return isStatic ? -1 : 1;
20 | }
21 | return Long.compare(offset, o.offset);
22 | }
23 |
24 | @Override
25 | public String toString() {
26 | if (isStatic) {
27 | return "static " + typeName + ' ' + name + " @ 0x" + Long.toHexString(offset);
28 | } else {
29 | return typeName + ' ' + name + " @ " + offset;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/hotspot/JVM.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | import net.intelie.introspective.util.Preconditions;
4 | import net.intelie.introspective.util.UnsafeGetter;
5 | import sun.misc.Unsafe;
6 |
7 | import java.io.PrintStream;
8 | import java.util.*;
9 |
10 | public class JVM {
11 | public static final Unsafe unsafe = UnsafeGetter.get();
12 |
13 | private final Map types = new LinkedHashMap<>();
14 | private final Map constants = new LinkedHashMap<>();
15 |
16 | public JVM() {
17 | readVmTypes(readVmStructs());
18 | readVmIntConstants();
19 | readVmLongConstants();
20 | }
21 |
22 | private Map> readVmStructs() {
23 | long entry = getSymbol("gHotSpotVMStructs");
24 | long typeNameOffset = getSymbol("gHotSpotVMStructEntryTypeNameOffset");
25 | long fieldNameOffset = getSymbol("gHotSpotVMStructEntryFieldNameOffset");
26 | long typeStringOffset = getSymbol("gHotSpotVMStructEntryTypeStringOffset");
27 | long isStaticOffset = getSymbol("gHotSpotVMStructEntryIsStaticOffset");
28 | long offsetOffset = getSymbol("gHotSpotVMStructEntryOffsetOffset");
29 | long addressOffset = getSymbol("gHotSpotVMStructEntryAddressOffset");
30 | long arrayStride = getSymbol("gHotSpotVMStructEntryArrayStride");
31 |
32 | Map> structs = new HashMap<>();
33 |
34 | for (; ; entry += arrayStride) {
35 | String typeName = getStringRef(entry + typeNameOffset);
36 | String fieldName = getStringRef(entry + fieldNameOffset);
37 | if (fieldName == null) break;
38 |
39 | String typeString = getStringRef(entry + typeStringOffset);
40 | boolean isStatic = getInt(entry + isStaticOffset) != 0;
41 | long offset = getLong(entry + (isStatic ? addressOffset : offsetOffset));
42 |
43 | Set fields = structs.computeIfAbsent(typeName, k -> new TreeSet<>());
44 | fields.add(new Field(fieldName, typeString, offset, isStatic));
45 | }
46 |
47 | return structs;
48 | }
49 |
50 | private void readVmTypes(Map> structs) {
51 | long entry = getSymbol("gHotSpotVMTypes");
52 | long typeNameOffset = getSymbol("gHotSpotVMTypeEntryTypeNameOffset");
53 | long superclassNameOffset = getSymbol("gHotSpotVMTypeEntrySuperclassNameOffset");
54 | long isOopTypeOffset = getSymbol("gHotSpotVMTypeEntryIsOopTypeOffset");
55 | long isIntegerTypeOffset = getSymbol("gHotSpotVMTypeEntryIsIntegerTypeOffset");
56 | long isUnsignedOffset = getSymbol("gHotSpotVMTypeEntryIsUnsignedOffset");
57 | long sizeOffset = getSymbol("gHotSpotVMTypeEntrySizeOffset");
58 | long arrayStride = getSymbol("gHotSpotVMTypeEntryArrayStride");
59 |
60 | for (; ; entry += arrayStride) {
61 | String typeName = getStringRef(entry + typeNameOffset);
62 | if (typeName == null) break;
63 |
64 | String superclassName = getStringRef(entry + superclassNameOffset);
65 | boolean isOop = getInt(entry + isOopTypeOffset) != 0;
66 | boolean isInt = getInt(entry + isIntegerTypeOffset) != 0;
67 | boolean isUnsigned = getInt(entry + isUnsignedOffset) != 0;
68 | int size = getInt(entry + sizeOffset);
69 |
70 | Set fields = structs.get(typeName);
71 | types.put(typeName, new Type(typeName, superclassName, size, isOop, isInt, isUnsigned, fields));
72 | }
73 | }
74 |
75 | private void readVmIntConstants() {
76 | long entry = getSymbol("gHotSpotVMIntConstants");
77 | long nameOffset = getSymbol("gHotSpotVMIntConstantEntryNameOffset");
78 | long valueOffset = getSymbol("gHotSpotVMIntConstantEntryValueOffset");
79 | long arrayStride = getSymbol("gHotSpotVMIntConstantEntryArrayStride");
80 |
81 | for (; ; entry += arrayStride) {
82 | String name = getStringRef(entry + nameOffset);
83 | if (name == null) break;
84 |
85 | int value = getInt(entry + valueOffset);
86 | constants.put(name, value);
87 | }
88 | }
89 |
90 | private void readVmLongConstants() {
91 | long entry = getSymbol("gHotSpotVMLongConstants");
92 | long nameOffset = getSymbol("gHotSpotVMLongConstantEntryNameOffset");
93 | long valueOffset = getSymbol("gHotSpotVMLongConstantEntryValueOffset");
94 | long arrayStride = getSymbol("gHotSpotVMLongConstantEntryArrayStride");
95 |
96 | for (; ; entry += arrayStride) {
97 | String name = getStringRef(entry + nameOffset);
98 | if (name == null) break;
99 |
100 | long value = getLong(entry + valueOffset);
101 | constants.put(name, value);
102 | }
103 | }
104 |
105 | public byte getByte(long addr) {
106 | return unsafe.getByte(addr);
107 | }
108 |
109 | public int getInt(long addr) {
110 | return unsafe.getInt(addr);
111 | }
112 |
113 | public long getLong(long addr) {
114 | return unsafe.getLong(addr);
115 | }
116 |
117 | public long getAddress(long addr) {
118 | return unsafe.getAddress(addr);
119 | }
120 |
121 | public String getString(long addr) {
122 | if (addr == 0) return null;
123 |
124 | char[] chars = new char[40];
125 | int offset = 0;
126 | for (byte b; (b = getByte(addr + offset)) != 0; ) {
127 | if (offset >= chars.length) chars = Arrays.copyOf(chars, offset * 2);
128 | chars[offset++] = (char) b;
129 | }
130 | return new String(chars, 0, offset);
131 | }
132 |
133 | public String getStringRef(long addr) {
134 | return getString(getAddress(addr));
135 | }
136 |
137 | public long getSymbol(String name) {
138 | long address = Symbols.lookup(name);
139 | Preconditions.checkArgument(address != 0,
140 | "No such symbol: %s", name);
141 | return getLong(address);
142 | }
143 |
144 | public Type type(String name) {
145 | return types.get(name);
146 | }
147 |
148 | public Number constant(String name) {
149 | return constants.get(name);
150 | }
151 |
152 | public void dump(PrintStream out) {
153 | out.println("Constants:");
154 | for (Map.Entry entry : constants.entrySet()) {
155 | String type = entry.getValue() instanceof Long ? "long" : "int";
156 | out.println("const " + type + ' ' + entry.getKey() + " = " + entry.getValue());
157 | }
158 | out.println();
159 |
160 | out.println("Types:");
161 | for (Type type : types.values()) {
162 | out.println(type);
163 | }
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/hotspot/Symbols.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | import java.lang.reflect.InvocationTargetException;
4 | import java.lang.reflect.Method;
5 | import java.util.Locale;
6 |
7 | public class Symbols {
8 | private static final Method findNative;
9 | private static final ClassLoader classLoader;
10 |
11 | static {
12 | String os = System.getProperty("os.name").toLowerCase(Locale.ROOT);
13 | if (os.contains("windows")) {
14 | String vmName = System.getProperty("java.vm.name");
15 | String dll = vmName.contains("Client VM") ? "/bin/client/jvm.dll" : "/bin/server/jvm.dll";
16 | try {
17 | System.load(System.getProperty("java.home") + dll);
18 | } catch (UnsatisfiedLinkError e) {
19 | throw new IllegalStateException("Cannot find jvm.dll. Unsupported JVM?");
20 | }
21 | classLoader = Symbols.class.getClassLoader();
22 | } else {
23 | classLoader = null;
24 | }
25 |
26 | try {
27 | findNative = ClassLoader.class.getDeclaredMethod("findNative", ClassLoader.class, String.class);
28 | findNative.setAccessible(true);
29 | } catch (NoSuchMethodException e) {
30 | throw new IllegalStateException("Method ClassLoader.findNative not found");
31 | }
32 | }
33 |
34 | public static long lookup(String name) {
35 | try {
36 | return (Long) findNative.invoke(null, classLoader, name);
37 | } catch (InvocationTargetException e) {
38 | throw new IllegalArgumentException(e.getTargetException());
39 | } catch (IllegalAccessException e) {
40 | throw new IllegalArgumentException(e);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/hotspot/Type.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | import net.intelie.introspective.util.Preconditions;
4 |
5 | import java.util.*;
6 | import java.util.stream.Collectors;
7 |
8 | public class Type {
9 | private static final Field[] NO_FIELDS = new Field[0];
10 |
11 | public final String name;
12 | public final String superName;
13 | public final int size;
14 | public final boolean isOop;
15 | public final boolean isInt;
16 | public final boolean isUnsigned;
17 | public final Map fields;
18 |
19 | Type(String name, String superName, int size, boolean isOop, boolean isInt, boolean isUnsigned, Set fields) {
20 | this.name = name;
21 | this.superName = superName;
22 | this.size = size;
23 | this.isOop = isOop;
24 | this.isInt = isInt;
25 | this.isUnsigned = isUnsigned;
26 | this.fields = fields == null ? Collections.emptyMap() : fields.stream().collect(Collectors.toMap(x -> x.name, x -> x, (x, y) -> x, LinkedHashMap::new));
27 | }
28 |
29 | public Field field(String name) {
30 | return fields.get(name);
31 | }
32 |
33 | public long global(String name) {
34 | Field field = field(name);
35 | Preconditions.checkArgument(field.isStatic, "Static field expected");
36 | return field.offset;
37 | }
38 |
39 | public long offset(String name) {
40 | Field field = field(name);
41 | Preconditions.checkArgument(!field.isStatic, "Instance field expected");
42 | return field.offset;
43 | }
44 |
45 | @Override
46 | public String toString() {
47 | StringBuilder sb = new StringBuilder(name);
48 | if (superName != null) sb.append(" extends ").append(superName);
49 | sb.append(" @ ").append(size).append('\n');
50 | for (Field field : fields.values()) {
51 | sb.append(" ").append(field).append('\n');
52 | }
53 | return sb.toString();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/hotspot/VMThread.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | public class VMThread {
4 | private static final java.lang.reflect.Field eetop;
5 |
6 | static {
7 | try {
8 | eetop = Thread.class.getDeclaredField("eetop");
9 | eetop.setAccessible(true);
10 | } catch (NoSuchFieldException e) {
11 | throw new IllegalArgumentException("Thread.eetop field not found");
12 | }
13 | }
14 |
15 | public static long of(Thread javaThread) {
16 | try {
17 | return eetop.getLong(javaThread);
18 | } catch (IllegalAccessException e) {
19 | throw new IllegalArgumentException(e);
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/ArrayPeeler.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import java.lang.reflect.Array;
4 |
5 | public class ArrayPeeler implements ReferencePeeler {
6 | private static final Object[] EMPTY_ARRAY = new Object[0];
7 | private Object[] obj;
8 | private int it;
9 | private Object current;
10 |
11 | @Override
12 | public void clear() {
13 | this.obj = null;
14 | this.it = 0;
15 | this.current = null;
16 | }
17 |
18 | @Override
19 | public long resetTo(Class> clazz, Object value) {
20 | Class> componentType = clazz.getComponentType();
21 |
22 | this.obj = !componentType.isPrimitive() ? (Object[]) value : EMPTY_ARRAY;
23 | this.it = 0;
24 | this.current = null;
25 |
26 | return JVMPrimitives.getArrayBaseOffset(clazz) +
27 | JVMPrimitives.getPrimitive(componentType) * Array.getLength(value);
28 | }
29 |
30 | @Override
31 | public boolean moveNext() {
32 | //doing this way to save field accesses that cost a lot
33 | int it = this.it;
34 | Object[] obj = this.obj;
35 | int count = obj.length;
36 |
37 | while (it < count) {
38 | Object newObj = obj[it++];
39 | if (newObj != null) {
40 | this.it = it;
41 | this.current = newObj;
42 | return true;
43 | }
44 | }
45 | this.it = it;
46 | return false;
47 | }
48 |
49 | @Override
50 | public Object current() {
51 | return current;
52 | }
53 |
54 | @Override
55 | public Object currentIndex() {
56 | return it > 0 ? it - 1 : null;
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/ConstantDummyPeeler.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | public class ConstantDummyPeeler implements ReferencePeeler {
4 | private Object obj;
5 | private boolean eof;
6 |
7 | @Override
8 | public void clear() {
9 | this.eof = false;
10 | this.obj = null;
11 | }
12 |
13 | @Override
14 | public long resetTo(Class> clazz, Object value) {
15 | this.eof = value == null;
16 | this.obj = value;
17 | return 0;
18 | }
19 |
20 | @Override
21 | public boolean moveNext() {
22 | if (eof) return false;
23 | return eof = true;
24 | }
25 |
26 | @Override
27 | public Object current() {
28 | return obj;
29 | }
30 |
31 | @Override
32 | public Object currentIndex() {
33 | return null;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/FastFieldAccessor.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.util.UnsafeGetter;
4 | import sun.misc.Unsafe;
5 |
6 | import java.lang.reflect.Field;
7 | import java.util.ArrayDeque;
8 | import java.util.Deque;
9 | import java.util.Objects;
10 | import java.util.function.Supplier;
11 | import java.util.logging.Level;
12 | import java.util.logging.Logger;
13 |
14 | public class FastFieldAccessor {
15 | private static final Unsafe U = UnsafeGetter.get();
16 | private final Deque> accessors;
17 | private final String name;
18 | private final long offset;
19 | private final int declarationOrder;
20 | private Accessor currentAccessor;
21 |
22 | public FastFieldAccessor(int declarationOrder, Field field) {
23 | this(declarationOrder, field, true);
24 | }
25 |
26 | public FastFieldAccessor(int declarationOrder, Field field, boolean allowUnsafe) {
27 | this.name = field.getName();
28 | this.accessors = new ArrayDeque<>();
29 | this.declarationOrder = declarationOrder;
30 |
31 | if (allowUnsafe && U != null) {
32 | offset = UnsafeGetter.objectFieldOffset(field);
33 | this.accessors.addLast(() -> unsafeAccessor(field, offset));
34 | } else {
35 | offset = 0;
36 | }
37 | this.accessors.addLast(() -> reflectionAccessor(field));
38 | this.accessors.addLast(() -> obj -> null);
39 |
40 | moveToNextAccessor();
41 | }
42 |
43 | private static Accessor unsafeAccessor(Field field, long offset) {
44 | Class> type = field.getType();
45 | if (byte.class.equals(type))
46 | return x -> U.getByte(x, offset);
47 | if (short.class.equals(type))
48 | return x -> U.getShort(x, offset);
49 | if (int.class.equals(type))
50 | return x -> U.getInt(x, offset);
51 | if (long.class.equals(type))
52 | return x -> U.getLong(x, offset);
53 | if (float.class.equals(type))
54 | return x -> U.getFloat(x, offset);
55 | if (double.class.equals(type))
56 | return x -> U.getDouble(x, offset);
57 | if (boolean.class.equals(type))
58 | return x -> U.getBoolean(x, offset);
59 | if (char.class.equals(type))
60 | return x -> U.getChar(x, offset);
61 | return x -> U.getObject(x, offset);
62 | }
63 |
64 | private static Accessor reflectionAccessor(Field field) {
65 | field.setAccessible(true);
66 | return field::get;
67 | }
68 |
69 | public long offset() {
70 | return offset;
71 | }
72 |
73 | public int declarationOrder() {
74 | return declarationOrder;
75 | }
76 |
77 | private void moveToNextAccessor() {
78 | while (!accessors.isEmpty()) {
79 | try {
80 | currentAccessor = Objects.requireNonNull(accessors.pollFirst()).get();
81 | return;
82 | } catch (Throwable e) {
83 | Logger.getLogger(FastFieldAccessor.class.getName())
84 | .log(Level.INFO, "Error getting accessor for field '" + name + "'", e);
85 | }
86 | }
87 | }
88 |
89 | public Object get(Object target) {
90 | do {
91 | try {
92 | return currentAccessor.get(target);
93 | } catch (Throwable e) {
94 | Logger.getLogger(FastFieldAccessor.class.getName())
95 | .log(Level.INFO, "Error using accessor for field '" + name + "'", e);
96 | moveToNextAccessor();
97 | }
98 | } while (true);
99 | }
100 |
101 | public String name() {
102 | return name;
103 | }
104 |
105 | private interface Accessor {
106 | Object get(Object obj) throws Exception;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/GenericPeeler.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | public class GenericPeeler implements ReferencePeeler {
4 | private final ObjectPeeler object;
5 | private final ArrayPeeler array;
6 | private ReferencePeeler current;
7 |
8 | public GenericPeeler(ReflectionCache cache) {
9 | this.object = new ObjectPeeler(cache);
10 | this.array = new ArrayPeeler();
11 | }
12 |
13 | @Override
14 | public void clear() {
15 | current = null;
16 | object.clear();
17 | array.clear();
18 | }
19 |
20 | @Override
21 | public long resetTo(Class> clazz, Object value) {
22 | current = clazz.isArray() ? array : object;
23 | return current.resetTo(clazz, value);
24 | }
25 |
26 | @Override
27 | public boolean moveNext() {
28 | return current.moveNext();
29 | }
30 |
31 | @Override
32 | public Object current() {
33 | return current.current();
34 | }
35 |
36 | @Override
37 | public Object currentIndex() {
38 | return current.currentIndex();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/JVMPrimitives.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.util.UnsafeGetter;
4 | import sun.misc.Unsafe;
5 |
6 | import java.lang.reflect.Field;
7 | import java.util.logging.Level;
8 | import java.util.logging.Logger;
9 |
10 | public class JVMPrimitives {
11 | private static final int objectHeaderSize;
12 | private static final long oopSize;
13 | private static final Unsafe U = UnsafeGetter.get();
14 |
15 | private static final int byteOffset;
16 | private static final int shortOffset;
17 | private static final int intOffset;
18 | private static final int longOffset;
19 | private static final int floatOffset;
20 | private static final int doubleOffset;
21 | private static final int booleanOffset;
22 | private static final int charOffset;
23 | private static final int objectOffset;
24 |
25 | private static final StringFastPath stringFastPath;
26 |
27 | static {
28 | Experiments experiments = Experiments.get();
29 | oopSize = experiments.computeOopSize();
30 | objectHeaderSize = experiments.computeObjectHeaderSize();
31 | byteOffset = experiments.computeArrayBaseOffset(byte[].class);
32 | shortOffset = experiments.computeArrayBaseOffset(short[].class);
33 | intOffset = experiments.computeArrayBaseOffset(int[].class);
34 | longOffset = experiments.computeArrayBaseOffset(long[].class);
35 | floatOffset = experiments.computeArrayBaseOffset(float[].class);
36 | doubleOffset = experiments.computeArrayBaseOffset(double[].class);
37 | booleanOffset = experiments.computeArrayBaseOffset(boolean[].class);
38 | charOffset = experiments.computeArrayBaseOffset(char[].class);
39 | objectOffset = experiments.computeArrayBaseOffset(Object[].class);
40 | stringFastPath = new StringFastPath();
41 | }
42 |
43 | public static boolean isCompactStringsEnabled() {
44 | return stringFastPath.isCompactStringsEnabled();
45 | }
46 |
47 | public static long getFieldOffset(Field field) {
48 | return UnsafeGetter.objectFieldOffset(field);
49 | }
50 |
51 | public static long getPrimitive(Class> clazz) {
52 | if (clazz == byte.class) return 1;
53 | if (clazz == short.class) return 2;
54 | if (clazz == int.class) return 4;
55 | if (clazz == long.class) return 8;
56 | if (clazz == float.class) return 4;
57 | if (clazz == double.class) return 8;
58 | if (clazz == boolean.class) return 1;
59 | if (clazz == char.class) return 2;
60 | return oopSize;
61 | }
62 |
63 |
64 | public static long getArrayBaseOffset(Class> clazz) {
65 | if (clazz == byte[].class) return byteOffset;
66 | if (clazz == short[].class) return shortOffset;
67 | if (clazz == int[].class) return intOffset;
68 | if (clazz == long[].class) return longOffset;
69 | if (clazz == float[].class) return floatOffset;
70 | if (clazz == double[].class) return doubleOffset;
71 | if (clazz == boolean[].class) return booleanOffset;
72 | if (clazz == char[].class) return charOffset;
73 | return objectOffset;
74 | }
75 |
76 | public static long getObjectHeaderSize() {
77 | return objectHeaderSize;
78 | }
79 |
80 | public static long getOppSize() {
81 | return oopSize;
82 | }
83 |
84 | public static long getFastPath(Class> clazz, Object obj) {
85 | if (clazz == String.class) return stringFastPath.size((String) obj);
86 | if (clazz == Byte.class) return 12L + 1;
87 | if (clazz == Short.class) return 12L + 2;
88 | if (clazz == Integer.class) return 12L + 4;
89 | if (clazz == Long.class) return 12L + 8;
90 | if (clazz == Float.class) return 12L + 4;
91 | if (clazz == Double.class) return 12L + 8;
92 | if (clazz == Boolean.class) return 12L + 1;
93 | if (clazz == Character.class) return 12L + 2;
94 | return -1;
95 | }
96 |
97 | public static long align(long v) {
98 | return v + ((8 - (v & 7)) & 7);
99 | }
100 |
101 | private static class Experiments {
102 | private static final Experiments INSTANCE = new Experiments();
103 |
104 | public static Experiments get() {
105 | return INSTANCE;
106 | }
107 |
108 | public int computeOopSize() {
109 | return guessOopSize(8);
110 | }
111 |
112 | public int computeObjectHeaderSize() {
113 | return 12;
114 | }
115 |
116 | public int computeArrayBaseOffset(Class> klass) {
117 | return U.arrayBaseOffset(klass);
118 | }
119 |
120 | private int guessOopSize(int defaultValue) {
121 | int oopSize;
122 | try {
123 | long off1 = U.objectFieldOffset(CompressedOopsClass.class.getField("obj1"));
124 | long off2 = U.objectFieldOffset(CompressedOopsClass.class.getField("obj2"));
125 | oopSize = (int) Math.abs(off2 - off1);
126 | } catch (Throwable e) {
127 | Logger.getLogger(Experiments.class.getName())
128 | .log(Level.WARNING, "Unable to determine oopsize", e);
129 | return defaultValue;
130 | }
131 |
132 | return oopSize;
133 | }
134 |
135 |
136 | public static class CompressedOopsClass {
137 | public Object obj1;
138 | public Object obj2;
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/ObjectPeeler.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | public class ObjectPeeler implements ReferencePeeler {
4 | private final ReflectionCache cache;
5 | private ReflectionCache.Item cached;
6 | private Object obj;
7 | private int it;
8 | private int count;
9 |
10 | private Object current;
11 |
12 | public ObjectPeeler(ReflectionCache cache) {
13 | this.cache = cache;
14 | }
15 |
16 | @Override
17 | public void clear() {
18 | this.cached = null;
19 | this.obj = null;
20 | this.it = 0;
21 | this.count = 0;
22 | this.current = null;
23 | }
24 |
25 | @Override
26 | public long resetTo(Class> clazz, Object value) {
27 | this.cached = cache.get(clazz);
28 | this.obj = value;
29 | this.it = 0;
30 | this.current = null;
31 | this.count = cached.fieldCount();
32 | return cached.size();
33 |
34 | }
35 |
36 | @Override
37 | public boolean moveNext() {
38 | //doing this way to save field accesses that cost a lot
39 | int count = this.count;
40 | int it = this.it;
41 | Object thisObj = this.obj;
42 | ReflectionCache.Item cached = this.cached;
43 |
44 | while (it < count) {
45 | Object obj = cached.value(thisObj, it++);
46 | if (obj != null) {
47 | this.current = obj;
48 | this.it = it;
49 | return true;
50 | }
51 | }
52 | this.it = it;
53 | return false;
54 | }
55 |
56 | @Override
57 | public Object current() {
58 | return current;
59 | }
60 |
61 | @Override
62 | public Object currentIndex() {
63 | return it > 0 ? cached.fieldName(it - 1) : null;
64 | }
65 | }
66 |
67 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/ReferencePeeler.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | public interface ReferencePeeler {
4 | void clear();
5 |
6 | //returns the shallow size of instance
7 | long resetTo(Class> clazz, Object value);
8 |
9 | boolean moveNext();
10 |
11 | Object current();
12 |
13 | Object currentIndex();
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/ReflectionCache.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import java.lang.reflect.Field;
4 | import java.lang.reflect.Modifier;
5 | import java.util.*;
6 | import java.util.function.Predicate;
7 |
8 | public class ReflectionCache {
9 | private final Map, Item> cache;
10 | private final Predicate shouldFollow;
11 |
12 | public ReflectionCache() {
13 | this(x -> true);
14 | }
15 |
16 | public ReflectionCache(Predicate shouldFollow) {
17 | this.shouldFollow = shouldFollow;
18 | this.cache = new HashMap<>(); //better performance than IdentityHashMap in this case
19 | }
20 |
21 | public void clear() {
22 | cache.clear();
23 | }
24 |
25 | public Item get(Class> clazz) {
26 | Item item = cache.get(clazz);
27 | if (item == null)
28 | cache.put(clazz, item = new Item(clazz));
29 | return item;
30 | }
31 |
32 | public class Item {
33 | private final long size;
34 | private final FastFieldAccessor[] fields;
35 | private final Field[] rawField;
36 |
37 | public Item(Class> clazz) {
38 | List peelable = new ArrayList<>();
39 | List peelableRaw = new ArrayList<>();
40 |
41 | long size = JVMPrimitives.getObjectHeaderSize();
42 | int order = 0;
43 | while (clazz != null) {
44 | for (Field field : clazz.getDeclaredFields()) {
45 | if (Modifier.isStatic(field.getModifiers()))
46 | continue;
47 |
48 | size = Math.max(size, JVMPrimitives.getFieldOffset(field) + JVMPrimitives.getPrimitive(field.getType()));
49 | if (field.getType().isPrimitive() || !shouldFollow.test(field))
50 | continue;
51 | peelable.add(new FastFieldAccessor(++order, field));
52 | peelableRaw.add(field);
53 | }
54 | clazz = clazz.getSuperclass();
55 | }
56 |
57 | this.size = size;
58 | this.fields = peelable.toArray(new FastFieldAccessor[0]);
59 | this.rawField = peelableRaw.toArray(new Field[0]);
60 | }
61 |
62 | public Field field(int index) {
63 | return rawField[index];
64 | }
65 |
66 | public long size() {
67 | return size;
68 | }
69 |
70 | public int fieldCount() {
71 | return fields.length;
72 | }
73 |
74 | public String fieldName(int index) {
75 | return fields[index].name();
76 | }
77 |
78 | public Object value(Object target, int index) {
79 | return fields[index].get(target);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/reflect/StringFastPath.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.util.UnsafeGetter;
4 | import sun.misc.Unsafe;
5 |
6 | import java.lang.reflect.Field;
7 | import java.lang.reflect.Method;
8 | import java.lang.reflect.Modifier;
9 |
10 | public class StringFastPath {
11 | private final Unsafe U = UnsafeGetter.get();
12 | private final long coderOffset;
13 | private final int arrayOffset;
14 | private final long stringSize;
15 |
16 | public StringFastPath() {
17 | Field field = null;
18 | try {
19 | Field compactStringsField = String.class.getDeclaredField("COMPACT_STRINGS");
20 | trySetAccessible(compactStringsField);
21 | if (Boolean.TRUE.equals(compactStringsField.get(null)))
22 | field = String.class.getDeclaredField("coder");
23 | } catch (NoSuchFieldException | IllegalAccessException ignored) {
24 | }
25 | coderOffset = field != null ? U.objectFieldOffset(field) : -1;
26 | arrayOffset = U.arrayBaseOffset(field != null ? byte[].class : char[].class);
27 | stringSize = computeStringSize();
28 | }
29 |
30 | private static void trySetAccessible(Field field) {
31 | try {
32 | Method trySetAccessible = Field.class.getMethod("trySetAccessible");
33 | trySetAccessible.invoke(field);
34 | } catch (ReflectiveOperationException e) {
35 | field.setAccessible(true);
36 | }
37 | }
38 |
39 | private long computeStringSize() {
40 | long size = JVMPrimitives.getObjectHeaderSize();
41 | for (Field field : String.class.getDeclaredFields()) {
42 | if (Modifier.isStatic(field.getModifiers()))
43 | continue;
44 |
45 | size = Math.max(size, JVMPrimitives.getFieldOffset(field) + JVMPrimitives.getPrimitive(field.getType()));
46 | }
47 | return size;
48 | }
49 |
50 | public long size(String s) {
51 | if (isCompactStringsEnabled()) {
52 | byte coder = U.getByte(s, coderOffset);
53 | return JVMPrimitives.align(stringSize) + JVMPrimitives.align(((long) s.length() << coder) + arrayOffset);
54 | } else {
55 | return JVMPrimitives.align(stringSize) + JVMPrimitives.align(arrayOffset + s.length() * 2L);
56 | }
57 | }
58 |
59 | public boolean isCompactStringsEnabled() {
60 | return coderOffset >= 0;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/BloomVisitedSet.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import java.util.Arrays;
4 |
5 | public class BloomVisitedSet implements VisitedSet {
6 | private final long[] table;
7 | private final int seed;
8 | private final int k;
9 | private final int mask;
10 |
11 | public BloomVisitedSet(int m, int k) {
12 | this(m, k, 0);
13 | }
14 |
15 | public BloomVisitedSet(int m, int k, int seed) {
16 | Preconditions.checkArgument(Integer.bitCount(m) == 1, "Table size (%s) must be a power of two", m);
17 | this.table = new long[m >>> 6];
18 | this.seed = mix(seed);
19 | this.mask = (table.length - 1) << 6;
20 | this.k = k;
21 | }
22 |
23 | public static int mix(int h) {
24 | h ^= h >>> 16;
25 | h *= 0x85ebca6b;
26 | h ^= h >>> 13;
27 | h *= 0xc2b2ae35;
28 | h ^= h >>> 16;
29 | return h;
30 | }
31 |
32 | @Override
33 | public void clear() {
34 | Arrays.fill(table, 0);
35 | }
36 |
37 | @Override
38 | public boolean softClear() {
39 | clear();
40 | return false;
41 | }
42 |
43 | @Override
44 | public int enter(Object obj) {
45 | long[] table = this.table;
46 | int h = System.identityHashCode(obj);
47 | int k = this.k;
48 | int mask = this.mask;
49 |
50 | int answer = -1;
51 |
52 | for (int i = 0; i < k; i++) {
53 | int h2 = mix(h + i + seed);
54 | int index = (h2 & mask) >>> 6;
55 | long lowermask = 1L << (h2 & 63);
56 | long value = table[index];
57 | if ((value & lowermask) == 0) {
58 | answer = 1;
59 | table[index] = value | lowermask;
60 | }
61 | }
62 | return answer;
63 | }
64 |
65 | @Override
66 | public boolean exit(Object obj, int hint) {
67 | return true;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/ExpiringVisitedSet.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import java.util.Arrays;
4 |
5 | public class ExpiringVisitedSet implements VisitedSet {
6 | private final int requiredSize;
7 | private final int rehashThreshold;
8 | private final int mask;
9 | private final Object[] table;
10 | private final int[] gen;
11 | //used only for rehashing
12 | private final Object[] tempTable;
13 | private final int[] tempGen;
14 |
15 | public long DEBUG_COLLISIONS = 0;
16 | public long DEBUG_REHASHES = 0;
17 | public long DEBUG_REHASHES_TIME = 0;
18 | public long DEBUG_HARDCLEARS = 0;
19 | public long DEBUG_HARDCLEARS_TIME = 0;
20 | public long DEBUG_EXIT_MISS = 0;
21 |
22 | private int minValue;
23 | private int maxValue;
24 | private int currentEnter;
25 | private int currentExit;
26 |
27 | public ExpiringVisitedSet(int requiredSize) {
28 | this(requiredSize, 3 * requiredSize, 4 * requiredSize);
29 | }
30 |
31 | public ExpiringVisitedSet(int requiredSize, int rehashThreshold, int tableSize) {
32 | Preconditions.checkArgument(0 <= requiredSize, "Required size (%d) must be non-negative", requiredSize);
33 | Preconditions.checkArgument(requiredSize <= rehashThreshold,
34 | "Rehash threshold (%s) must be at least as large as required size (%s)", rehashThreshold, requiredSize);
35 | Preconditions.checkArgument(rehashThreshold < tableSize,
36 | "Table size (%s) must be stricly greater than rehash threshold (%s)", tableSize, rehashThreshold);
37 | Preconditions.checkArgument(Integer.bitCount(tableSize) == 1, "Table size (%s) must be a power of two",
38 | tableSize);
39 | this.requiredSize = requiredSize;
40 | this.rehashThreshold = rehashThreshold;
41 | this.table = new Object[tableSize];
42 | this.gen = new int[tableSize];
43 |
44 | this.tempTable = new Object[requiredSize];
45 | this.tempGen = new int[requiredSize];
46 | this.mask = tableSize - 1;
47 | clearGen();
48 | }
49 |
50 | @Override
51 | public void clear() {
52 | clearGen();
53 | Arrays.fill(tempTable, null);
54 | Arrays.fill(table, null);
55 | }
56 |
57 | private void clearGen() {
58 | Arrays.fill(gen, currentEnter = currentExit = minValue = Integer.MIN_VALUE);
59 | maxValue = minValue + rehashThreshold; //new maximum
60 | }
61 |
62 | @Override
63 | public boolean softClear() {
64 | if (maxValue > Integer.MAX_VALUE - rehashThreshold) {
65 | DEBUG_HARDCLEARS++;
66 | long DEBUG_START = System.nanoTime();
67 | clearGen();
68 | DEBUG_HARDCLEARS_TIME += System.nanoTime() - DEBUG_START;
69 | return false;
70 | } else {
71 | currentEnter = currentExit = minValue = maxValue; //reset minima to latest maximum
72 | maxValue = minValue + rehashThreshold; //new maximum
73 | return true;
74 | }
75 | }
76 |
77 | private int findIndex(Object obj) {
78 | //this code is repeated in enter() for speed
79 | int index = System.identityHashCode(obj) & mask;
80 |
81 | int collisions = 0;
82 | while (gen[index] > minValue) {
83 | if (table[index] == obj)
84 | return ~index;
85 | //quadratic probing
86 | collisions++;
87 | index = (index + collisions) & mask;
88 | }
89 |
90 | DEBUG_COLLISIONS += collisions;
91 | return index;
92 | }
93 |
94 | @Override
95 | public int enter(Object obj) {
96 | if (currentExit + requiredSize <= currentEnter)
97 | return Integer.MIN_VALUE; //queue is full
98 |
99 | //same code as in findIndex(), inlined here for performance
100 | int index = System.identityHashCode(obj) & mask;
101 |
102 | int collisions = 0;
103 | while (gen[index] > minValue) {
104 | if (table[index] == obj)
105 | return ~index;
106 | //quadratic probing
107 | collisions++;
108 | index = (index + collisions) & mask;
109 | }
110 |
111 | DEBUG_COLLISIONS += collisions;
112 | table[index] = obj;
113 | gen[index] = maxValue;
114 | if (++currentEnter >= maxValue)
115 | rehash();
116 | return index;
117 | }
118 |
119 | @Override
120 | public boolean exit(Object obj, int index) {
121 | if (gen[index] <= minValue || table[index] != obj) {
122 | DEBUG_EXIT_MISS++;
123 | index = ~findIndex(obj);
124 | if (index < 0) return false;
125 | }
126 | gen[index] = ++currentExit;
127 | return true;
128 | }
129 |
130 | public int contains(Object obj) {
131 | return ~findIndex(obj);
132 | }
133 |
134 | private void rehash() {
135 | DEBUG_REHASHES++;
136 | long DEBUG_START = System.nanoTime();
137 |
138 | //we only keep the "requiredSize" most recent items after rehash
139 | int cutGen = Math.max(currentEnter - requiredSize, minValue);
140 | //not checking underflow here because rehash is only called if currentEnter == maxValue
141 | //and maxValue >= MIN+rehashThreshold, which is >= requiredSize, so currentEnter >= MIN+requiredSize
142 | assert cutGen <= currentEnter;
143 | //trust but verify
144 |
145 | int count = 0;
146 | int exits = 0;
147 |
148 | for (int i = 0; i < table.length; i++) {
149 | int geni = gen[i];
150 | if (geni > cutGen) {
151 | tempTable[count] = table[i];
152 |
153 | //we will reset generation numbers in clearGen, so
154 | //we have to recompute object generations accordingly
155 | if (geni == maxValue) {
156 | tempGen[count] = rehashThreshold;
157 | } else {
158 | tempGen[count] = geni - cutGen;
159 | exits++;
160 | }
161 |
162 | count++;
163 | }
164 | }
165 | assert currentEnter - currentExit <= count && count <= currentEnter - cutGen;
166 |
167 | clearGen();
168 | for (int i = 0; i < count; i++) {
169 | Object obj = tempTable[i];
170 | int index = findIndex(obj);
171 | table[index] = obj;
172 | gen[index] = minValue + tempGen[i];
173 | }
174 | currentEnter = minValue + count;
175 | currentExit = minValue + exits;
176 |
177 | DEBUG_REHASHES_TIME += System.nanoTime() - DEBUG_START;
178 | }
179 |
180 | }
181 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/IdentityVisitedSet.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import net.intelie.introspective.util.VisitedSet;
4 |
5 | import java.util.IdentityHashMap;
6 |
7 | public class IdentityVisitedSet implements VisitedSet {
8 | private final IdentityHashMap set;
9 |
10 | public IdentityVisitedSet() {
11 | this.set = new IdentityHashMap<>();
12 | }
13 |
14 | @Override
15 | public void clear() {
16 | set.clear();
17 | }
18 |
19 | @Override
20 | public boolean softClear() {
21 | set.clear();
22 | return false;
23 | }
24 |
25 | @Override
26 | public int enter(Object obj) {
27 | return set.put(obj, true) == null ? 1 : -1;
28 | }
29 |
30 | @Override
31 | public boolean exit(Object obj, int hint) {
32 | return true;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/Preconditions.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2007 The Guava Authors
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 |
15 | package net.intelie.introspective.util;
16 |
17 | public abstract class Preconditions {
18 | public static void checkArgument(boolean expression) {
19 | if (!expression) {
20 | throw new IllegalArgumentException();
21 | }
22 | }
23 |
24 | public static void checkArgument(boolean expression, Object errorMessage) {
25 | if (!expression) {
26 | throw new IllegalArgumentException(String.valueOf(errorMessage));
27 | }
28 | }
29 |
30 | public static void checkArgument(boolean expression,
31 | String errorMessageTemplate,
32 | Object... errorMessageArgs) {
33 | if (!expression) {
34 | throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs));
35 | }
36 | }
37 |
38 | public static void checkState(boolean expression) {
39 | if (!expression) {
40 | throw new IllegalStateException();
41 | }
42 | }
43 |
44 | public static void checkState(boolean expression, Object errorMessage) {
45 | if (!expression) {
46 | throw new IllegalStateException(String.valueOf(errorMessage));
47 | }
48 | }
49 |
50 | public static void checkState(boolean expression,
51 | String errorMessageTemplate,
52 | Object... errorMessageArgs) {
53 | if (!expression) {
54 | throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs));
55 | }
56 | }
57 |
58 | public static T checkNotNull(T reference) {
59 | if (reference == null) {
60 | throw new NullPointerException();
61 | }
62 | return reference;
63 | }
64 |
65 | public static T checkNotNull(T reference, Object errorMessage) {
66 | if (reference == null) {
67 | throw new NullPointerException(String.valueOf(errorMessage));
68 | }
69 | return reference;
70 | }
71 |
72 | public static T checkNotNull(T reference,
73 | String errorMessageTemplate,
74 | Object... errorMessageArgs) {
75 | if (reference == null) {
76 | // If either of these parameters is null, the right thing happens anyway
77 | throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs));
78 | }
79 | return reference;
80 | }
81 |
82 | public static int checkElementIndex(int index, int size) {
83 | return checkElementIndex(index, size, "index");
84 | }
85 |
86 | public static int checkElementIndex(
87 | int index, int size, String desc) {
88 | // Carefully optimized for execution by hotspot (explanatory comment above)
89 | if (index < 0 || index >= size) {
90 | throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
91 | }
92 | return index;
93 | }
94 |
95 | private static String badElementIndex(int index, int size, String desc) {
96 | if (index < 0) {
97 | return format("%s (%s) must not be negative", desc, index);
98 | } else if (size < 0) {
99 | throw new IllegalArgumentException("negative size: " + size);
100 | } else { // index >= size
101 | return format("%s (%s) must be less than size (%s)", desc, index, size);
102 | }
103 | }
104 |
105 | public static int checkPositionIndex(int index, int size) {
106 | return checkPositionIndex(index, size, "index");
107 | }
108 |
109 | public static int checkPositionIndex(int index, int size, String desc) {
110 | // Carefully optimized for execution by hotspot (explanatory comment above)
111 | if (index < 0 || index > size) {
112 | throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
113 | }
114 | return index;
115 | }
116 |
117 | private static String badPositionIndex(int index, int size, String desc) {
118 | if (index < 0) {
119 | return format("%s (%s) must not be negative", desc, index);
120 | } else if (size < 0) {
121 | throw new IllegalArgumentException("negative size: " + size);
122 | } else { // index > size
123 | return format("%s (%s) must not be greater than size (%s)", desc, index, size);
124 | }
125 | }
126 |
127 | public static void checkPositionIndexes(int start, int end, int size) {
128 | // Carefully optimized for execution by hotspot (explanatory comment above)
129 | if (start < 0 || end < start || end > size) {
130 | throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
131 | }
132 | }
133 |
134 | private static String badPositionIndexes(int start, int end, int size) {
135 | if (start < 0 || start > size) {
136 | return badPositionIndex(start, size, "start index");
137 | }
138 | if (end < 0 || end > size) {
139 | return badPositionIndex(end, size, "end index");
140 | }
141 | // end < start
142 | return format("end index (%s) must not be less than start index (%s)", end, start);
143 | }
144 |
145 | public static String format(String template, Object... args) {
146 | template = String.valueOf(template); // null -> "null"
147 |
148 | // start substituting the arguments into the '%s' placeholders
149 | StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
150 | int templateStart = 0;
151 | int i = 0;
152 | while (i < args.length) {
153 | int placeholderStart = template.indexOf("%s", templateStart);
154 | if (placeholderStart == -1) {
155 | break;
156 | }
157 | builder.append(template, templateStart, placeholderStart);
158 | builder.append(args[i++]);
159 | templateStart = placeholderStart + 2;
160 | }
161 | builder.append(template.substring(templateStart));
162 |
163 | // if we run out of placeholders, append the extra args in square braces
164 | if (i < args.length) {
165 | builder.append(" [");
166 | builder.append(args[i++]);
167 | while (i < args.length) {
168 | builder.append(", ");
169 | builder.append(args[i++]);
170 | }
171 | builder.append(']');
172 | }
173 |
174 | return builder.toString();
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/UnsafeGetter.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import sun.misc.Unsafe;
4 |
5 | import java.lang.invoke.MethodHandle;
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.invoke.MethodType;
8 | import java.lang.reflect.Field;
9 | import java.security.AccessController;
10 | import java.security.PrivilegedAction;
11 | import java.util.logging.Level;
12 | import java.util.logging.Logger;
13 |
14 | public class UnsafeGetter {
15 | private static final Unsafe unsafe = tryGetAccessible(Unsafe.class, "theUnsafe");
16 | private static final MethodHandle objectFieldOffset = tryGetRealMethodHandle(Unsafe.class,
17 | "theInternalUnsafe", "theUnsafe", "objectFieldOffset");
18 |
19 | public static Unsafe tryGetAccessible(Class clazz, String fieldName) {
20 | return AccessController.doPrivileged((PrivilegedAction) () -> {
21 | try {
22 | Field unsafe = clazz.getDeclaredField(fieldName);
23 | unsafe.setAccessible(true);
24 | return (Unsafe) unsafe.get(null);
25 | } catch (Throwable e) {
26 | throw new IllegalStateException(e);
27 | }
28 | }
29 | );
30 | }
31 |
32 | public static MethodHandle tryGetRealMethodHandle(Class clazz, String internalFieldName, String fieldName,
33 | String methodName) {
34 | return AccessController.doPrivileged((PrivilegedAction) () -> {
35 | try {
36 | Field field = clazz.getDeclaredField(internalFieldName);
37 | field.setAccessible(true);
38 | Object object = field.get(null);
39 |
40 | return MethodHandles.lookup().findVirtual(object.getClass(), methodName,
41 | MethodType.methodType(long.class, Field.class)).bindTo(object);
42 | } catch (NoSuchFieldException ignored) {
43 | // Internal Unsafe does not exist, fall back to legacy field.
44 | } catch (IllegalAccessException e) {
45 | Logger.getLogger(UnsafeGetter.class.getName())
46 | .log(Level.WARNING, "Internal field access rejected, estimating lambda size can fail", e);
47 | // Fall back to legacy field.
48 | } catch (ReflectiveOperationException e) {
49 | throw new RuntimeException(e);
50 | }
51 |
52 | try {
53 | Field field = clazz.getDeclaredField(fieldName);
54 | field.setAccessible(true);
55 | Object object = field.get(null);
56 |
57 | return MethodHandles.lookup().findVirtual(object.getClass(), methodName,
58 | MethodType.methodType(long.class, Field.class)).bindTo(object);
59 | } catch (ReflectiveOperationException e) {
60 | throw new RuntimeException(e);
61 | }
62 | });
63 | }
64 |
65 | public static Unsafe get() {
66 | return unsafe;
67 | }
68 |
69 | public static MethodHandle getObjectFieldOffset() {
70 | return objectFieldOffset;
71 | }
72 |
73 | public static long objectFieldOffset(Field field) {
74 | try {
75 | return (long) objectFieldOffset.invokeExact(field);
76 | } catch (Throwable throwable) {
77 | if (throwable instanceof RuntimeException) {
78 | throw (RuntimeException) throwable;
79 | }
80 | if (throwable instanceof Error) {
81 | throw (Error) throwable;
82 | }
83 | throw new RuntimeException(throwable);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/main/java/net/intelie/introspective/util/VisitedSet.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | public interface VisitedSet {
4 | void clear();
5 |
6 | boolean softClear();
7 |
8 | int enter(Object obj);
9 |
10 | boolean exit(Object obj, int hint);
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/BloomObjectSizerTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.reflect.ReflectionCache;
4 | import net.intelie.introspective.reflect.StringFastPath;
5 | import net.intelie.introspective.reflect.TestSizeUtils;
6 | import net.intelie.introspective.util.ExpiringVisitedSet;
7 | import net.intelie.introspective.util.IdentityVisitedSet;
8 | import org.junit.Test;
9 |
10 | import java.util.*;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 | public class BloomObjectSizerTest {
15 | @Test
16 | public void estimateSingleton() {
17 | Map test = Collections.singletonMap("abc", 123);
18 |
19 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
20 | }
21 |
22 | @Test
23 | public void wontBreakOnDeepLinkedList() {
24 | Map test = new LinkedHashMap<>();
25 | for (int i = 0; i < 100; i++) {
26 | test.put(i, i);
27 | }
28 |
29 | BloomObjectSizer sizer = new BloomObjectSizer(new ReflectionCache(), 1024, 1, 10);
30 | sizer.visit(test);
31 | assertThat(sizer.skipped()).isGreaterThan(0);
32 | }
33 |
34 |
35 | @Test
36 | public void estimateSizerSize() {
37 |
38 | ReflectionCache cache = new ReflectionCache(f -> !f.getType().equals(ReflectionCache.class));
39 |
40 | BloomObjectSizer sizer = new BloomObjectSizer(
41 | cache, //ignore cache itself
42 | 1 << 20,
43 | (1 << 16) - 1, //adding -1 because of https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8164793
44 | 1 << 10
45 | );
46 | int expected = (1 << 20) / 8 + (1 << 16) * 4 + (1 << 10) * 96;
47 | assertThat(realEstimate(cache, sizer)).isBetween((long) (expected * 0.9), (long) (expected * 1.1));
48 | }
49 |
50 | @Test
51 | public void estimateMap() {
52 | Map test = new HashMap<>();
53 | test.put(111, Arrays.asList("aaa", 222));
54 | test.put(333.0, Collections.singletonMap("bbb", 444));
55 |
56 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
57 | }
58 |
59 | @Test
60 | public void estimateNull() {
61 | assertThat(estimate(null)).isEqualTo(0);
62 | }
63 |
64 | @Test
65 | public void estimateLinkedList() {
66 | List test = new LinkedList<>();
67 | for (int i = 0; i < 1000; i++)
68 | test.add(i);
69 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
70 | }
71 |
72 | @Test
73 | public void estimateLinkedListOverMaxDepth() {
74 | BloomObjectSizer sizer = new BloomObjectSizer(new ReflectionCache(), 1 << 20, 1, 200);
75 | for (int k = 0; k < 10; k++) {
76 | List test = new LinkedList<>();
77 | for (int i = 0; i < 1000; i++)
78 | test.add(i);
79 |
80 | sizer.softClear();
81 | sizer.visit(test);
82 |
83 | assertThat(sizer.bytes()).isLessThan(TestSizeUtils.size(test));
84 | assertThat(sizer.count()).isEqualTo(797);
85 |
86 | }
87 | }
88 |
89 | @Test
90 | public void estimateVisitedSet() {
91 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 15);
92 | int expected = (1 << 20) + (1 << 18);
93 | assertThat(estimate(set)).isBetween((long) (expected * 0.9), (long) (expected * 1.1));
94 | }
95 |
96 | @Test
97 | public void estimateLinkedHashMap() {
98 | Map test = new LinkedHashMap<>();
99 | test.put(111, Arrays.asList("aaa", 222));
100 | test.put(333.0, Collections.singletonMap("bbb", 444));
101 |
102 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
103 | }
104 |
105 | @Test
106 | public void testGrowingString() {
107 | StringFastPath fast = new StringFastPath();
108 | String s = "";
109 | while (s.length() < 1024) {
110 | assertThat(estimate(s)).isEqualTo(TestSizeUtils.size(s));
111 | s = s + s + 'x';
112 | }
113 | }
114 |
115 | private long estimate(Object obj) {
116 | BloomObjectSizer sizer = new BloomObjectSizer(new ReflectionCache(), 1 << 20, 1000, 1000);
117 | sizer.clear();
118 | sizer.visit(obj);
119 | return sizer.bytes();
120 | }
121 |
122 | private long realEstimate(ReflectionCache cache, Object obj) {
123 | ObjectSizer real = new ObjectSizer(cache, new IdentityVisitedSet(), 1 << 15);
124 | real.resetTo(obj);
125 | long total = 0;
126 | while (real.moveNext()) {
127 | total += real.bytes();
128 | }
129 | return total;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/ObjectSizerTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.reflect.ReflectionCache;
4 | import net.intelie.introspective.reflect.StringFastPath;
5 | import net.intelie.introspective.reflect.TestSizeUtils;
6 | import net.intelie.introspective.util.BloomVisitedSet;
7 | import net.intelie.introspective.util.ExpiringVisitedSet;
8 | import net.intelie.introspective.util.IdentityVisitedSet;
9 | import org.junit.Test;
10 | import org.openjdk.jol.info.ClassLayout;
11 | import org.openjdk.jol.vm.LightVM;
12 |
13 | import java.io.Serializable;
14 | import java.util.*;
15 | import java.util.function.Function;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 |
19 | public class ObjectSizerTest {
20 | @Test
21 | public void estimateSingleton() {
22 | Map test = Collections.singletonMap("abc", 123);
23 |
24 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
25 | }
26 |
27 | @Test
28 | public void estimateMap() {
29 | Map test = new HashMap<>();
30 | test.put(111, Arrays.asList("aaa", 222));
31 | test.put(333.0, Collections.singletonMap("bbb", 444));
32 |
33 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
34 | }
35 |
36 | @Test
37 | public void wontBreakOnDeepLinkedList() {
38 | Map test = new LinkedHashMap<>();
39 | for (int i = 0; i < 100; i++) {
40 | test.put(i, i);
41 | }
42 |
43 | ObjectSizer sizer = new ObjectSizer(new ReflectionCache(), new IdentityVisitedSet(), 10);
44 | sizer.resetTo(test);
45 | while (sizer.moveNext()) {
46 | }
47 | assertThat(sizer.skipped()).isGreaterThan(0);
48 | }
49 |
50 | @Test
51 | public void estimateNull() {
52 | assertThat(estimate(null)).isEqualTo(0);
53 | }
54 |
55 | @Test
56 | public void estimateLinkedList() {
57 | List test = new LinkedList<>();
58 | for (int i = 0; i < 1000; i++)
59 | test.add(i);
60 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
61 | }
62 |
63 | @Test
64 | public void estimateLinkedListOverMaxDepth() {
65 | List test = new LinkedList<>();
66 | for (int i = 0; i < 1000; i++)
67 | test.add(i);
68 |
69 | long size = 0, count = 0;
70 | ObjectSizer sizer = new ObjectSizer(new ReflectionCache(), new IdentityVisitedSet(), 200);
71 | sizer.resetTo(test);
72 | while (sizer.moveNext()) {
73 | size += sizer.bytes();
74 | count++;
75 | }
76 |
77 | assertThat(size).isLessThan(TestSizeUtils.size(test));
78 | assertThat(count).isEqualTo(791);
79 | }
80 |
81 | @Test
82 | public void estimateVisitedSet() {
83 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 15);
84 | int expected = (1 << 20) + (1 << 18);
85 | assertThat(estimate(set)).isBetween((long) (expected * 0.9), (long) (expected * 1.1));
86 | }
87 |
88 | @Test
89 | public void estimateLinkedHashMap() {
90 | Map test = new LinkedHashMap<>();
91 | test.put(111, Arrays.asList("aaa", 222));
92 | test.put(333.0, Collections.singletonMap("bbb", 444));
93 |
94 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test));
95 | }
96 |
97 | @Test
98 | public void estimateLambda() {
99 | Integer obj = 1;
100 | Function, ?> test = (Function) x -> x + obj;
101 |
102 | assertThat(estimate(obj)).isEqualTo(TestSizeUtils.size(obj));
103 | assertThat(estimate(test)).isEqualTo(TestSizeUtils.size(test)).isGreaterThan(TestSizeUtils.size(obj));
104 | }
105 |
106 | @Test
107 | public void testGrowingString() {
108 | StringFastPath fast = new StringFastPath();
109 | String s = "";
110 | while (s.length() < 1024) {
111 | assertThat(estimate(s)).isEqualTo(TestSizeUtils.size(s));
112 | s = s + s + 'x';
113 | }
114 | }
115 |
116 | @Test
117 | public void fullTest() throws ClassNotFoundException {
118 | fullTestWith(new ObjectSizer());
119 | fullTestWith(new ObjectSizer(new ReflectionCache(), new ExpiringVisitedSet(1 << 15), 1 << 15));
120 | fullTestWith(new ObjectSizer(new ReflectionCache(), new BloomVisitedSet(1 << 20, 2), 1 << 15));
121 | fullTestWith(new ObjectSizer(new ReflectionCache(), new IdentityVisitedSet(), 1 << 15));
122 | }
123 |
124 | private void fullTestWith(ObjectSizer sizer) throws ClassNotFoundException {
125 | Map test = new LinkedHashMap<>();
126 | Object value1 = Arrays.asList("aaa", 222);
127 | Object value2 = Collections.singletonMap("bbb", 444);
128 | test.put(111, value1);
129 | test.put(333.0, value2);
130 |
131 | TestClass obj = new TestClass(test);
132 |
133 | sizer.resetTo(obj);
134 |
135 | assertIteratorSame(sizer, obj, 0, "");
136 | assertIteratorEq(sizer, (byte) 1, 1, ".boxedByte");
137 | assertIteratorEq(sizer, (short) 2, 1, ".boxedShort");
138 | assertIteratorEq(sizer, 3, 1, ".boxedInt");
139 | assertIteratorEq(sizer, (long) 4, 1, ".boxedLong");
140 | assertIteratorEq(sizer, (float) 5, 1, ".boxedFloat");
141 | assertIteratorEq(sizer, (double) 6, 1, ".boxedDouble");
142 | assertIteratorEq(sizer, true, 1, ".boxedBool");
143 | assertIteratorEq(sizer, 'x', 1, ".boxedChar");
144 | assertIteratorSame(sizer, test, 1, ".this$0");
145 | assertIteratorUnknown(sizer, Class.forName("java.util.LinkedHashMap$Entry"), 2, ".this$0.head");
146 | assertIteratorUnknown(sizer, Class.forName("java.util.LinkedHashMap$Entry"), 3, ".this$0.head.after");
147 | assertIteratorEq(sizer, 333.0, 4, ".this$0.head.after.key");
148 | assertIteratorSame(sizer, value2, 4, ".this$0.head.after.value");
149 | assertIteratorEqFull(sizer, "bbb", 5, ".this$0.head.after.value.k");
150 | assertIteratorEq(sizer, 444, 5, ".this$0.head.after.value.v");
151 | assertIteratorEq(sizer, 111, 3, ".this$0.head.key");
152 | assertIteratorSame(sizer, value1, 3, ".this$0.head.value");
153 | assertOnlyPath(sizer, Serializable[].class, 4, ".this$0.head.value.a");
154 | assertIteratorEqFull(sizer, "aaa", 5, ".this$0.head.value.a[0]");
155 | assertIteratorEq(sizer, 222, 5, ".this$0.head.value.a[1]");
156 | assertOnlyPath(sizer, Class.forName("[Ljava.util.HashMap$Node;"), 2, ".this$0.table");
157 | assertIteratorSame(sizer, this, 1, ".this$0$");
158 | assertThat(sizer.skipChildren()).isTrue();
159 | assertThat(sizer.skipped()).isGreaterThan(0);
160 |
161 | assertThat(sizer.moveNext()).isFalse();
162 | }
163 |
164 | @Test
165 | public void fullTestWithSkip() {
166 | Map test = new LinkedHashMap<>();
167 | Object value1 = Arrays.asList("aaa", 222);
168 | Object value2 = Collections.singletonMap("bbb", 444);
169 | test.put(111, value1);
170 | test.put(333.0, value2);
171 |
172 | TestClass obj = new TestClass(test);
173 |
174 | ObjectSizer sizer = new ObjectSizer();
175 | sizer.resetTo(obj);
176 |
177 | assertIteratorSame(sizer, obj, 0, "");
178 | assertIteratorEq(sizer, (byte) 1, 1, ".boxedByte");
179 | assertIteratorEq(sizer, (short) 2, 1, ".boxedShort");
180 | assertIteratorEq(sizer, 3, 1, ".boxedInt");
181 | assertIteratorEq(sizer, (long) 4, 1, ".boxedLong");
182 | assertIteratorEq(sizer, (float) 5, 1, ".boxedFloat");
183 | assertIteratorEq(sizer, (double) 6, 1, ".boxedDouble");
184 | assertIteratorEq(sizer, true, 1, ".boxedBool");
185 | assertIteratorEq(sizer, 'x', 1, ".boxedChar");
186 | assertThat(sizer.skipChildren()).isFalse();
187 |
188 | assertIteratorSame(sizer, test, 1, ".this$0");
189 | assertThat(sizer.skipChildren()).isTrue();
190 |
191 | assertIteratorSame(sizer, this, 1, ".this$0$");
192 | assertThat(sizer.skipChildren()).isTrue();
193 |
194 | assertThat(sizer.moveNext()).isFalse();
195 | }
196 |
197 | private void assertIteratorEq(ObjectSizer sizer, Object obj, int depth, String path) {
198 | assertOnlyPath(sizer, obj.getClass(), depth, path);
199 | assertThat(sizer.current()).isEqualTo(obj);
200 | assertBytes(sizer, LightVM.current().sizeOf(obj));
201 | }
202 |
203 | private void assertIteratorEqFull(ObjectSizer sizer, Object obj, int depth, String path) {
204 | assertOnlyPath(sizer, obj.getClass(), depth, path);
205 | assertThat(sizer.current()).isEqualTo(obj);
206 | assertBytes(sizer, TestSizeUtils.size(obj));
207 | }
208 |
209 | private void assertBytes(ObjectSizer sizer, long expectedSize) {
210 | assertThat(sizer.bytes()).isEqualTo(expectedSize);
211 | assertThat(sizer.unalignedBytes()).isBetween(expectedSize - 7, expectedSize);
212 | }
213 |
214 | private void assertIteratorSame(ObjectSizer sizer, Object obj, int depth, String path) {
215 | assertOnlyPath(sizer, obj.getClass(), depth, path);
216 | assertThat(sizer.current()).isSameAs(obj);
217 | assertBytes(sizer, LightVM.current().sizeOf(obj));
218 | }
219 |
220 | private void assertIteratorUnknown(ObjectSizer sizer, Class> clazz, int depth, String path) {
221 | assertOnlyPath(sizer, clazz, depth, path);
222 | LightVM.current();
223 | assertThat(sizer.current()).isNotNull();
224 | assertBytes(sizer, ClassLayout.parseClass(clazz).instanceSize());
225 | }
226 |
227 | private void assertOnlyPath(ObjectSizer sizer, Class> clazz, int depth, String path) {
228 | assertThat(sizer.moveNext()).isTrue();
229 | assertThat(sizer.path()).isEqualTo(path);
230 | assertThat(sizer.type()).isEqualTo(clazz);
231 | assertThat(sizer.visitDepth()).isEqualTo(depth);
232 | }
233 |
234 | private long estimate(Object obj) {
235 | long size = 0, size2 = 0;
236 | ObjectSizer sizer = new ObjectSizer();
237 | sizer.resetTo(obj);
238 | while (sizer.moveNext())
239 | size += sizer.bytes();
240 | sizer.clear();
241 | sizer.resetTo(obj);
242 | while (sizer.moveNext())
243 | size2 += sizer.bytes();
244 | assertThat(size).isEqualTo(size2);
245 | return size;
246 | }
247 |
248 | private class TestClass {
249 | private byte primByte;
250 | private short primShort;
251 | private int primInt;
252 | private long primLong;
253 | private float primFloat;
254 | private double primDouble;
255 | private boolean primBool;
256 | private char primChar;
257 |
258 | private Byte boxedByte = 1;
259 | private Short boxedShort = 2;
260 | private Integer boxedInt = 3;
261 | private Long boxedLong = 4L;
262 | private Float boxedFloat = 5f;
263 | private Double boxedDouble = 6d;
264 | private Boolean boxedBool = true;
265 | private Character boxedChar = 'x';
266 |
267 | private Map, ?> this$0;
268 |
269 | public TestClass(Map, ?> string) {
270 | this.this$0 = string;
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/Playground.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import net.intelie.introspective.hotspot.JVM;
4 | import net.intelie.introspective.reflect.ObjectPeeler;
5 | import net.intelie.introspective.reflect.ReflectionCache;
6 | import net.intelie.introspective.util.BloomVisitedSet;
7 | import net.intelie.introspective.util.ExpiringVisitedSet;
8 | import net.intelie.introspective.util.SuppressForbidden;
9 | import org.junit.Ignore;
10 | import org.junit.Test;
11 |
12 | import java.util.*;
13 | import java.util.stream.IntStream;
14 |
15 | @Ignore
16 | @SuppressForbidden
17 | public class Playground {
18 | @Test
19 | public void name() {
20 | JVM jvm = new JVM();
21 | jvm.dump(System.out);
22 | }
23 |
24 | @Test
25 | public void testSmallObject() {
26 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 15);
27 | ObjectSizer sizer = new ObjectSizer(new ReflectionCache(), set, 1 << 15);
28 | Map test = new HashMap<>();
29 | test.put(111, Arrays.asList("aaa", 222));
30 | test.put(333.0, Collections.singletonMap("bbb", 444));
31 |
32 | testSizer(sizer, test, 10000000);
33 | System.out.println("Collisions: " + set.DEBUG_COLLISIONS);
34 | System.out.println("Rehashes: " + set.DEBUG_REHASHES);
35 | System.out.println("Rehashes (time): " + set.DEBUG_REHASHES_TIME / 1e9);
36 | System.out.println("Hard clears: " + set.DEBUG_HARDCLEARS);
37 | System.out.println("Hard clears (time): " + set.DEBUG_HARDCLEARS_TIME / 1e9);
38 | System.out.println("Exit misses: " + set.DEBUG_EXIT_MISS);
39 | }
40 |
41 | @Test
42 | public void testSmallObjectBfs() {
43 | BloomObjectSizer sizer = new BloomObjectSizer(new ReflectionCache(), 1 << 10, 1 << 15, 1 << 15);
44 | Map test = new HashMap<>();
45 | test.put(111, Arrays.asList("aaa", 222));
46 | test.put(333.0, Collections.singletonMap("bbb", 444));
47 |
48 | testBfsSizer(sizer, test, 10000000);
49 | }
50 |
51 | @Test
52 | public void testLargeObject() {
53 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 15);
54 | ObjectSizer sizer = new ObjectSizer(new ReflectionCache(), new BloomVisitedSet(1 << 24, 3), 1 << 15);
55 |
56 | Object[] objs = IntStream.range(0, 10000).mapToObj(x -> {
57 | Map test = new HashMap<>();
58 | test.put(111 + x * 10000, Arrays.asList("aaa" + x, 222 + x * 10000));
59 | test.put(333.0 + x * 10000, Collections.singletonMap("bbb" + x, 444 + x * 10000));
60 | return test;
61 | }).toArray(Object[]::new);
62 |
63 | testSizer(sizer, objs, 1000);
64 | System.out.println("Collisions: " + set.DEBUG_COLLISIONS);
65 | System.out.println("Rehashes: " + set.DEBUG_REHASHES);
66 | System.out.println("Rehashes (time): " + set.DEBUG_REHASHES_TIME / 1e9);
67 | System.out.println("Hard clears: " + set.DEBUG_HARDCLEARS);
68 | System.out.println("Hard clears (time): " + set.DEBUG_HARDCLEARS_TIME / 1e9);
69 | System.out.println("Exit misses: " + set.DEBUG_EXIT_MISS);
70 |
71 | }
72 |
73 | @Test
74 | public void testLargeObjectBfs() {
75 | BloomObjectSizer sizer = new BloomObjectSizer(new ReflectionCache(), 1 << 20, 1 << 15, 1 << 15);
76 |
77 | Object[] objs = IntStream.range(0, 10000).mapToObj(x -> {
78 | Map test = new HashMap<>();
79 | test.put(111 + x * 10000, Arrays.asList("aaa" + x, 222 + x * 10000));
80 | test.put(333.0 + x * 10000, Collections.singletonMap("bbb" + x, 444 + x * 10000));
81 | return test;
82 | }).toArray(Object[]::new);
83 |
84 | testBfsSizer(sizer, objs, 1000);
85 |
86 | }
87 |
88 | private void testSizer(ObjectSizer sizer, Object test, int measureCount) {
89 | for (int i = 0; i < measureCount / 100; i++) {
90 | sizer.resetTo(test);
91 | while (sizer.moveNext()) ;
92 | }
93 |
94 | long start = System.nanoTime();
95 | long memStart = ThreadResources.allocatedBytes(Thread.currentThread());
96 | long total = 0;
97 | for (int i = 0; i < measureCount; i++) {
98 | sizer.resetTo(test);
99 | while (sizer.moveNext()) total += 1;
100 | }
101 | long memEnd = ThreadResources.allocatedBytes(Thread.currentThread());
102 | System.out.println("Allocation: " + (memEnd - memStart));
103 | System.out.println("Total: " + total);
104 | long end = System.nanoTime();
105 | System.out.println("Time: " + (end - start) / 1e9);
106 | System.out.println("Objects/s: " + (long) (total * 1e9 / (end - start)));
107 | }
108 |
109 | private void testBfsSizer(BloomObjectSizer sizer, Object test, int measureCount) {
110 | for (int i = 0; i < measureCount / 100; i++) {
111 | sizer.softClear();
112 | sizer.visit(test);
113 | }
114 |
115 | long start = System.nanoTime();
116 | long memStart = ThreadResources.allocatedBytes(Thread.currentThread());
117 | long total = 0;
118 | for (int i = 0; i < measureCount; i++) {
119 | sizer.softClear();
120 | sizer.visit(test);
121 | total += sizer.count();
122 | }
123 | long memEnd = ThreadResources.allocatedBytes(Thread.currentThread());
124 | System.out.println("Allocation: " + (memEnd - memStart));
125 | System.out.println("Total: " + total);
126 | long end = System.nanoTime();
127 | System.out.println("Time: " + (end - start) / 1e9);
128 | System.out.println("Objects/s: " + (long) (total * 1e9 / (end - start)));
129 | }
130 |
131 | @Test
132 | public void testVisitedSet() {
133 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 16);
134 |
135 | Object[] objs = IntStream.range(0, 1000).mapToObj(x -> new Object()).toArray();
136 |
137 | for (int i = 0; i < 100000; i++) {
138 | set.softClear();
139 | for (int j = 0; j < 13; j++) {
140 | int index = set.enter(objs[j]);
141 | set.exit(objs[j], index);
142 | }
143 | }
144 |
145 | long start = System.nanoTime();
146 | long memStart = ThreadResources.allocatedBytes(Thread.currentThread());
147 |
148 | for (int i = 0; i < 10000000; i++) {
149 | set.softClear();
150 | for (int j = 0; j < 13; j++) {
151 | int index = set.enter(objs[j]);
152 | //set.exit(objs[j], index);
153 | }
154 | }
155 | long memEnd = ThreadResources.allocatedBytes(Thread.currentThread());
156 |
157 | System.out.println("Allocation: " + (memEnd - memStart));
158 | System.out.println("Time: " + (System.nanoTime() - start) / 1e9);
159 | // System.out.println("Collisions: " + set.DEBUG_COLLISIONS);
160 | // System.out.println("Rehashes: " + set.DEBUG_REHASHES);
161 | // System.out.println("Rehashes (time): " + set.DEBUG_REHASHES_TIME / 1e9);
162 | // System.out.println("Hard clears: " + set.DEBUG_HARDCLEARS);
163 | // System.out.println("Hard clears (time): " + set.DEBUG_HARDCLEARS_TIME / 1e9);
164 | // System.out.println("Exit misses: " + set.DEBUG_EXIT_MISS);
165 | }
166 |
167 | @Test
168 | public void testPeeler() {
169 | ObjectPeeler peeler = new ObjectPeeler(new ReflectionCache());
170 |
171 | Map obj = new LinkedHashMap<>();
172 | obj.put(111, Arrays.asList("aaa", 222));
173 | obj.put(333.0, Collections.singletonMap("bbb", 444));
174 | Class> clazz = obj.getClass();
175 |
176 | for (int i = 0; i < 10000; i++) {
177 | peeler.resetTo(clazz, obj);
178 | while (peeler.moveNext()) ;
179 | }
180 |
181 | long start = System.nanoTime();
182 | long total = 0;
183 | for (int i = 0; i < 100000000; i++) {
184 | peeler.resetTo(clazz, obj);
185 | while (peeler.moveNext()) total++;
186 | }
187 | System.out.println(total);
188 | System.out.println((System.nanoTime() - start) / 1e9);
189 |
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/ThreadResourcesTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective;
2 |
3 | import com.sun.management.ThreadMXBean;
4 | import net.intelie.introspective.util.SuppressForbidden;
5 | import org.junit.Before;
6 | import org.junit.Test;
7 |
8 | import java.lang.management.ManagementFactory;
9 | import java.util.Locale;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | public class ThreadResourcesTest {
14 | private ThreadMXBean bean;
15 |
16 | @Before
17 | public void setUp() {
18 | bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
19 | //warmup
20 | diff();
21 | }
22 |
23 | @Test
24 | @SuppressForbidden
25 | public void testEverythingValid() {
26 | System.out.println("RUNNING FROM: " + System.getProperty("java.version"));
27 | if (!System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac os")) {
28 | assertThat(ThreadResources.isValidTlab()).isTrue();
29 | assertThat(ThreadResources.isValidAllocated()).isTrue();
30 | }
31 | }
32 |
33 | @Test
34 | public void testEnabled() {
35 | for (int i = 0; i < 1000; i++)
36 | diff();
37 |
38 | long total = 0;
39 | for (int i = 0; i < 1000; i++) {
40 | total += diff();
41 | }
42 | assertThat(total / 1000).isZero();
43 | }
44 |
45 | @Test
46 | public void testWillGetByteArrayAllocation() {
47 | long start = ThreadResources.allocatedBytes();
48 |
49 | byte[] bytes = new byte[100000];
50 |
51 | long total = ThreadResources.allocatedBytes() - start;
52 | if (!System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac os"))
53 | assertThat(total).isBetween(100000L, 100000L + 100);
54 | else
55 | assertThat(total).isZero();
56 | }
57 |
58 | private long diff() {
59 | long check = bean.getThreadAllocatedBytes(Thread.currentThread().getId());
60 | long mine = ThreadResources.allocatedBytes(Thread.currentThread());
61 | if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("mac os")) {
62 | assertThat(mine).isZero();
63 | return 0;
64 | }
65 | assertThat(mine).isGreaterThanOrEqualTo(check);
66 | return mine - check;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/hotspot/JVMTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.hotspot;
2 |
3 | import org.junit.BeforeClass;
4 | import org.junit.Test;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.PrintStream;
8 | import java.io.UnsupportedEncodingException;
9 |
10 | import static org.assertj.core.api.Assertions.assertThat;
11 |
12 | public class JVMTest {
13 |
14 | public static JVM jvm;
15 |
16 | @BeforeClass
17 | public static void setUp() {
18 | jvm = new JVM();
19 | }
20 |
21 | @Test
22 | public void testListStuff() throws UnsupportedEncodingException {
23 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
24 |
25 | jvm.dump(new PrintStream(baos, false, "UTF-8"));
26 |
27 | String str = baos.toString("UTF-8");
28 |
29 | assertThat(str).contains("uint32_t @ 4");
30 | }
31 |
32 | @Test
33 | public void testGetStuff() {
34 | assertThat(jvm.constant("oopSize")).isIn(4, 8);
35 | Type type = jvm.type("Arguments");
36 |
37 | long javaComand = type.global("_java_command");
38 | long numJvmArgs = type.global("_num_jvm_args");
39 |
40 | assertThat(jvm.getStringRef(javaComand)).isNotEmpty();
41 | assertThat(jvm.getInt(numJvmArgs)).isBetween(0, 255);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/ArrayPeelerTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.ThreadResources;
4 | import org.junit.Test;
5 | import org.openjdk.jol.vm.LightVM;
6 |
7 | import java.util.function.IntFunction;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | public class ArrayPeelerTest {
12 | @Test
13 | public void testPrimitive() {
14 | long[] obj = {1, 2, 3};
15 |
16 | ArrayPeeler peeler = new ArrayPeeler();
17 | long bytes = peeler.resetTo(long[].class, obj);
18 |
19 | assertThat(bytes).isEqualTo(LightVM.current().sizeOf(obj));
20 | assertThat(peeler.moveNext()).isFalse();
21 | }
22 |
23 | @Test
24 | public void testObject() {
25 | Integer[] obj = {1, 2, 3, null, 4};
26 |
27 | ArrayPeeler peeler = new ArrayPeeler();
28 | long bytes = peeler.resetTo(Integer[].class, obj);
29 |
30 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(obj));
31 | assertNext(peeler, 0, 1);
32 | assertNext(peeler, 1, 2);
33 | assertNext(peeler, 2, 3);
34 | assertNext(peeler, 4, 4);
35 | assertThat(peeler.moveNext()).isFalse();
36 | }
37 |
38 | @Test
39 | public void testString() {
40 | char[] obj = "abcdefgh".toCharArray();
41 |
42 | ArrayPeeler peeler = new ArrayPeeler();
43 | long bytes = peeler.resetTo(char[].class, obj);
44 |
45 | assertThat(bytes).isEqualTo(LightVM.current().sizeOf(obj));
46 | assertThat(peeler.moveNext()).isFalse();
47 | }
48 |
49 | @Test
50 | public void testStringGrowing() {
51 | StringBuilder s = new StringBuilder();
52 | while (s.length() < 1024) {
53 | char[] obj = s.toString().toCharArray();
54 |
55 | ArrayPeeler peeler = new ArrayPeeler();
56 | long bytes = peeler.resetTo(char[].class, obj);
57 |
58 | assertThat(JVMPrimitives.align(bytes))
59 | .describedAs("%d", s.length())
60 | .isEqualTo(LightVM.current().sizeOf(obj));
61 | assertThat(peeler.moveNext()).isFalse();
62 |
63 | s.append(s).append("x");
64 | }
65 | }
66 |
67 | @Test
68 | public void testGrowingSizes() {
69 | assertSizing(byte[]::new);
70 | assertSizing(short[]::new);
71 | assertSizing(int[]::new);
72 | assertSizing(long[]::new);
73 | assertSizing(float[]::new);
74 | assertSizing(double[]::new);
75 | assertSizing(boolean[]::new);
76 | assertSizing(char[]::new);
77 | }
78 |
79 | private void assertSizing(IntFunction fn) {
80 | for (int i = 0; i < 1024; i = i * 2 + 1) {
81 | Object obj = fn.apply(i);
82 | ArrayPeeler peeler = new ArrayPeeler();
83 |
84 | long bytes = peeler.resetTo(obj.getClass(), obj);
85 |
86 | assertThat(JVMPrimitives.align(bytes))
87 | .describedAs("%d", i)
88 | .isEqualTo(LightVM.current().sizeOf(obj));
89 |
90 | }
91 | }
92 |
93 | @Test
94 | public void testAllocations() {
95 | ArrayPeeler peeler = new ArrayPeeler();
96 | Integer[] obj = {1, 2, 3};
97 | peeler.resetTo(Integer[].class, obj);
98 | while (peeler.moveNext()) ;
99 |
100 | long start = ThreadResources.allocatedBytes(Thread.currentThread());
101 | for (int i = 0; i < 1000; i++) {
102 | peeler.resetTo(Integer[].class, obj);
103 | while (peeler.moveNext()) ;
104 | }
105 | assertThat((ThreadResources.allocatedBytes(Thread.currentThread()) - start) / 1000).isEqualTo(0);
106 | }
107 |
108 | private void assertNext(ReferencePeeler peeler, Object index, Object value) {
109 | assertThat(peeler.moveNext()).isTrue();
110 | assertThat(peeler.currentIndex()).isEqualTo(index);
111 | assertThat(peeler.current()).isEqualTo(value);
112 | }
113 |
114 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/FastFieldAccessorTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import org.junit.Test;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | import static org.assertj.core.api.Assertions.assertThat;
8 |
9 | public class FastFieldAccessorTest {
10 | @Test
11 | public void testNormalCase() throws NoSuchFieldException {
12 | assertAccessor("privateValue", "42");
13 | }
14 |
15 | @Test
16 | public void testPublicCase() throws NoSuchFieldException {
17 | assertAccessor(TestClass.class.getField("publicValue"), "43");
18 | assertAccessor("publicValue", "43");
19 | }
20 |
21 | @Test
22 | public void testPrimitives() throws NoSuchFieldException {
23 | assertAccessor("primByte", (byte) 42);
24 | assertAccessor("primShort", (short) 42);
25 | assertAccessor("primInt", (int) 42);
26 | assertAccessor("primLong", (long) 42);
27 | assertAccessor("primFloat", 42f);
28 | assertAccessor("primDouble", 42d);
29 | assertAccessor("primBool", true);
30 | assertAccessor("primChar", 'x');
31 | }
32 |
33 |
34 | private void assertAccessor(String fieldName, Object expectedValue) throws NoSuchFieldException {
35 | Field field = TestClass.class.getDeclaredField(fieldName);
36 | assertAccessor(field, expectedValue);
37 | }
38 |
39 | private void assertAccessor(Field field, Object expectedValue) {
40 | TestClass test = new TestClass();
41 | FastFieldAccessor accessor = new FastFieldAccessor(42, field);
42 | assertThat(accessor.get(test)).isEqualTo(expectedValue);
43 | assertThat(accessor.name()).isEqualTo(field.getName());
44 | assertThat(accessor.declarationOrder()).isEqualTo(42);
45 | assertThat(accessor.offset()).isGreaterThan(0);
46 |
47 | FastFieldAccessor accessor2 = new FastFieldAccessor(0, field, false);
48 | assertThat(accessor2.get(test)).isEqualTo(expectedValue);
49 | assertThat(accessor2.name()).isEqualTo(field.getName());
50 | }
51 |
52 | private static class TestClass {
53 | public String publicValue = "43";
54 | private String privateValue = "42";
55 | private byte primByte = 42;
56 | private short primShort = 42;
57 | private int primInt = 42;
58 | private long primLong = 42;
59 | private float primFloat = 42;
60 | private double primDouble = 42;
61 | private boolean primBool = true;
62 | private char primChar = 'x';
63 | }
64 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/JVMPrimitivesTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class JVMPrimitivesTest {
8 | @Test
9 | public void testCompactStrings() {
10 | assertThat(JVMPrimitives.isCompactStringsEnabled()).isEqualTo(
11 | !System.getProperty("java.version").contains("1.8"));
12 | }
13 |
14 | @Test
15 | public void testPrimitives() {
16 | assertThat(JVMPrimitives.getPrimitive(byte.class)).isEqualTo(1);
17 | assertThat(JVMPrimitives.getPrimitive(short.class)).isEqualTo(2);
18 | assertThat(JVMPrimitives.getPrimitive(int.class)).isEqualTo(4);
19 | assertThat(JVMPrimitives.getPrimitive(long.class)).isEqualTo(8);
20 | assertThat(JVMPrimitives.getPrimitive(float.class)).isEqualTo(4);
21 | assertThat(JVMPrimitives.getPrimitive(double.class)).isEqualTo(8);
22 | assertThat(JVMPrimitives.getPrimitive(boolean.class)).isEqualTo(1);
23 | assertThat(JVMPrimitives.getPrimitive(char.class)).isEqualTo(2);
24 |
25 | assertThat(JVMPrimitives.getPrimitive(Long.class)).isEqualTo(4); //oopsize
26 | }
27 |
28 | @Test
29 | public void testExtended() {
30 | assertThat(JVMPrimitives.getFastPath(String.class, "ccc")).isEqualTo(TestSizeUtils.size("ccc"));
31 | assertThat(JVMPrimitives.getFastPath(Byte.class, null)).isEqualTo(12 + 1);
32 | assertThat(JVMPrimitives.getFastPath(Short.class, null)).isEqualTo(12 + 2);
33 | assertThat(JVMPrimitives.getFastPath(Integer.class, null)).isEqualTo(12 + 4);
34 | assertThat(JVMPrimitives.getFastPath(Long.class, null)).isEqualTo(12 + 8);
35 | assertThat(JVMPrimitives.getFastPath(Float.class, null)).isEqualTo(12 + 4);
36 | assertThat(JVMPrimitives.getFastPath(Double.class, null)).isEqualTo(12 + 8);
37 | assertThat(JVMPrimitives.getFastPath(Boolean.class, null)).isEqualTo(12 + 1);
38 | assertThat(JVMPrimitives.getFastPath(Character.class, null)).isEqualTo(12 + 2);
39 | assertThat(JVMPrimitives.getFastPath(Object.class, null)).isEqualTo(-1);
40 | }
41 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/ObjectPeelerTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.ThreadResources;
4 | import org.junit.Test;
5 | import org.openjdk.jol.vm.LightVM;
6 |
7 | import java.util.ArrayList;
8 | import java.util.HashMap;
9 | import java.util.List;
10 |
11 | import static org.assertj.core.api.Assertions.assertThat;
12 |
13 | public class ObjectPeelerTest {
14 | private final ReflectionCache cache = new ReflectionCache(x -> !x.isSynthetic());
15 |
16 | @Test
17 | public void testSimpleClass() {
18 | TestClass obj = new TestClass("abc");
19 |
20 | ObjectPeeler peeler = new ObjectPeeler(cache);
21 | long bytes = peeler.resetTo(TestClass.class, obj);
22 |
23 | LightVM.current();
24 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(obj));
25 | //remember this is also a reference
26 | assertThat(bytes).isEqualTo(12 + 32 + 10 * JVMPrimitives.getOppSize());
27 |
28 | assertNext(peeler, "boxedByte", (byte) 1);
29 | assertNext(peeler, "boxedShort", (short) 2);
30 | assertNext(peeler, "boxedInt", 3);
31 | assertNext(peeler, "boxedLong", 4L);
32 | assertNext(peeler, "boxedFloat", 5f);
33 | assertNext(peeler, "boxedDouble", 6d);
34 | assertNext(peeler, "boxedBool", true);
35 | assertNext(peeler, "boxedChar", 'x');
36 | assertNext(peeler, "this$0", "abc");
37 |
38 | assertThat(peeler.moveNext()).isFalse();
39 | }
40 |
41 | @Test
42 | public void testNewHashMap() {
43 | HashMap map = new HashMap<>();
44 |
45 | ObjectPeeler peeler = new ObjectPeeler(cache);
46 | long bytes = peeler.resetTo(HashMap.class, map);
47 |
48 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(map));
49 | }
50 |
51 | @Test
52 | public void testBoxedLong() {
53 | ObjectPeeler peeler = new ObjectPeeler(cache);
54 | long bytes = peeler.resetTo(Long.class, 123L);
55 |
56 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(123L));
57 | }
58 |
59 | @Test
60 | public void testString() {
61 | String s = "";
62 | for (int i = 0; i < 100; i++) {
63 | s += "x";
64 | ObjectPeeler peeler = new ObjectPeeler(cache);
65 | long bytes = peeler.resetTo(String.class, s);
66 |
67 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(s));
68 | }
69 | }
70 |
71 | @Test
72 | public void testNewArrayList() {
73 | List list = new ArrayList<>();
74 |
75 | ObjectPeeler peeler = new ObjectPeeler(cache);
76 | long bytes = peeler.resetTo(ArrayList.class, list);
77 |
78 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(list));
79 | }
80 |
81 | @Test
82 | public void testSimpleStaticClass() {
83 | TestStaticClass obj = new TestStaticClass("abc");
84 |
85 | ObjectPeeler peeler = new ObjectPeeler(cache);
86 | long bytes = peeler.resetTo(TestStaticClass.class, obj);
87 |
88 | LightVM.current();
89 | assertThat(JVMPrimitives.align(bytes)).isEqualTo(LightVM.current().sizeOf(obj));
90 | assertThat(bytes).isEqualTo(12 + 32 + 9 * JVMPrimitives.getOppSize());
91 |
92 | assertNext(peeler, "boxedByte", (byte) 1);
93 | assertNext(peeler, "boxedShort", (short) 2);
94 | assertNext(peeler, "boxedInt", 3);
95 | assertNext(peeler, "boxedLong", 4L);
96 | assertNext(peeler, "boxedFloat", 5f);
97 | assertNext(peeler, "boxedDouble", 6d);
98 | assertNext(peeler, "boxedBool", true);
99 | assertNext(peeler, "boxedChar", 'x');
100 | assertNext(peeler, "this$0", "abc");
101 | assertThat(peeler.moveNext()).isFalse();
102 | }
103 |
104 | @Test
105 | public void testAllocations() {
106 | ObjectPeeler peeler = new ObjectPeeler(cache);
107 | TestClass obj = new TestClass("abc");
108 | peeler.resetTo(TestClass.class, obj);
109 | while (peeler.moveNext()) ;
110 |
111 | long start = ThreadResources.allocatedBytes(Thread.currentThread());
112 | for (int i = 0; i < 1000; i++) {
113 | peeler.resetTo(TestClass.class, obj);
114 | while (peeler.moveNext()) ;
115 | }
116 | assertThat((ThreadResources.allocatedBytes(Thread.currentThread()) - start) / 1000).isEqualTo(0);
117 | }
118 |
119 | private void assertNext(ReferencePeeler peeler, Object index, Object value) {
120 | assertThat(peeler.moveNext()).isTrue();
121 | assertThat(peeler.currentIndex()).isEqualTo(index);
122 | assertThat(peeler.current()).isEqualTo(value);
123 | }
124 |
125 | private static class TestStaticClass {
126 | private static String MUST_IGNORE = "ABCDEFGHIJ";
127 | private byte primByte;
128 | private short primShort;
129 | private int primInt;
130 | private long primLong;
131 | private float primFloat;
132 | private double primDouble;
133 | private boolean primBool;
134 | private char primChar;
135 |
136 | private Byte boxedByte = 1;
137 | private Short boxedShort = 2;
138 | private Integer boxedInt = 3;
139 | private Long boxedLong = 4L;
140 | private Float boxedFloat = 5f;
141 | private Double boxedDouble = 6d;
142 | private Boolean boxedBool = true;
143 | private Character boxedChar = 'x';
144 |
145 | private String this$0;
146 |
147 | public TestStaticClass(String string) {
148 | this.this$0 = string;
149 | }
150 | }
151 |
152 | private class TestClass {
153 | private byte primByte;
154 | private short primShort;
155 | private int primInt;
156 | private long primLong;
157 | private float primFloat;
158 | private double primDouble;
159 | private boolean primBool;
160 | private char primChar;
161 |
162 | private Byte boxedByte = 1;
163 | private Short boxedShort = 2;
164 | private Integer boxedInt = 3;
165 | private Long boxedLong = 4L;
166 | private Float boxedFloat = 5f;
167 | private Double boxedDouble = 6d;
168 | private Boolean boxedBool = true;
169 | private Character boxedChar = 'x';
170 |
171 | private String this$0;
172 |
173 | public TestClass(String string) {
174 | this.this$0 = string;
175 | }
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/ReflectionCacheTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import net.intelie.introspective.util.UnsafeGetter;
4 | import org.junit.Test;
5 | import org.openjdk.jol.info.ClassData;
6 | import org.openjdk.jol.info.ClassLayout;
7 | import org.openjdk.jol.layouters.CurrentLayouter;
8 | import org.openjdk.jol.vm.LightVM;
9 | import sun.misc.Unsafe;
10 |
11 | import java.lang.reflect.Field;
12 |
13 | import static org.assertj.core.api.Assertions.assertThat;
14 |
15 | public class ReflectionCacheTest {
16 | @Test
17 | public void testCaching() throws NoSuchFieldException {
18 | ReflectionCache cache = new ReflectionCache();
19 | cache.clear();
20 | ReflectionCache.Item item = cache.get(TestClass.class);
21 |
22 | assertThat(JVMPrimitives.align(item.size())).isEqualTo(alternativeSizing1(TestClass.class));
23 | assertThat(item.size()).isEqualTo(alternativeSizing2(TestClass.class));
24 | assertThat(item.fieldCount()).isEqualTo(10);
25 | assertThat(item.fieldName(0)).isEqualTo("boxedByte");
26 | assertThat(item.field(0)).isEqualTo(TestClass.class.getDeclaredField("boxedByte"));
27 |
28 | }
29 |
30 | private long alternativeSizing1(Class> clazz) {
31 | LightVM.current();
32 | ClassLayout layout = new CurrentLayouter().layout(ClassData.parseClass(clazz));
33 | return layout.instanceSize();
34 | }
35 |
36 | private long alternativeSizing2(Class> clazz) {
37 | Unsafe unsafe = UnsafeGetter.get();
38 | long max = 0;
39 | for (Field field : clazz.getDeclaredFields()) {
40 | max = Math.max(max, unsafe.objectFieldOffset(field) + JVMPrimitives.getPrimitive(field.getType()));
41 | }
42 | return max;
43 | }
44 |
45 | private class TestClass {
46 | private byte primByte;
47 | private short primShort;
48 | private int primInt;
49 | private long primLong;
50 | private float primFloat;
51 | private double primDouble;
52 | private boolean primBool;
53 | private char primChar;
54 |
55 | private Byte boxedByte = 1;
56 | private Short boxedShort = 2;
57 | private Integer boxedInt = 3;
58 | private Long boxedLong = 4L;
59 | private Float boxedFloat = 5f;
60 | private Double boxedDouble = 6d;
61 | private Boolean boxedBool = true;
62 | private Character boxedChar = 'x';
63 |
64 | private String this$0;
65 |
66 | public TestClass(String string) {
67 | this.this$0 = string;
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/StringFastPathTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class StringFastPathTest {
8 | @Test
9 | public void testGrowingStrings() {
10 | StringFastPath fast = new StringFastPath();
11 | String s = "";
12 | while (s.length() < 1024) {
13 | assertThat(fast.size(s)).isEqualTo(TestSizeUtils.size(s));
14 | s = s + s + 'x';
15 | }
16 | }
17 |
18 | @Test
19 | public void testGrowingUnicodeStrings() {
20 | StringFastPath fast = new StringFastPath();
21 | String s = "";
22 | while (s.length() < 1024) {
23 | assertThat(fast.size(s)).isEqualTo(TestSizeUtils.size(s));
24 | s = s + s + '€';
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/reflect/TestSizeUtils.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.reflect;
2 |
3 | import org.openjdk.jol.info.GraphWalker;
4 | import org.openjdk.jol.vm.LightVM;
5 |
6 | public class TestSizeUtils {
7 | public static long size(Object obj) {
8 | LightVM.current();
9 | return new GraphWalker().walk(obj).totalSize();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/BloomVisitedSetTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import org.junit.Ignore;
4 | import org.junit.Test;
5 |
6 | import java.util.Arrays;
7 | import java.util.stream.Collectors;
8 |
9 | import static org.assertj.core.api.Assertions.assertThat;
10 |
11 | public class BloomVisitedSetTest {
12 | @Test
13 | public void testMix() {
14 | int[] count = new int[32];
15 | int[] count2 = new int[32];
16 |
17 | int tests = 100000;
18 | for (int i = 0; i < tests; i++) {
19 | int hashed = BloomVisitedSet.mix(i);
20 | int hashed2 = BloomVisitedSet.mix(i + 1);
21 |
22 | for (int j = 0; j < 32; j++) {
23 | if ((hashed & 1 << j) != 0) {
24 | count[j]++;
25 | if ((hashed2 & 1 << j) != 0)
26 | count2[j]++;
27 | }
28 | }
29 | }
30 |
31 | for (int i = 0; i < 32; i++) {
32 | assertThat(count[i]).isBetween(tests * 49 / 100, tests * 51 / 100);
33 | assertThat(count2[i]).isBetween(tests * 24 / 100, tests * 26 / 100);
34 | }
35 | }
36 |
37 | @Test
38 | @Ignore
39 | @SuppressForbidden
40 | public void testError() {
41 | int total = 500000;
42 | double[][] error = new double[256][10];
43 | for (int i = 1; i <= 10; i++) {
44 | BloomVisitedSet set = new BloomVisitedSet(1 << 20, i);
45 | int failures = 0;
46 | for (int j = 1; j <= total; j++) {
47 | Object obj = new Object();
48 | if (set.enter(obj) < 0)
49 | failures++;
50 | assertThat(set.enter(obj)).isLessThan(0);
51 | set.exit(obj, 0);
52 |
53 | if (j % (total / 100) == 0)
54 | error[j / (total / 100) - 1][i - 1] = failures / (double) j;
55 | }
56 | }
57 | for (int j = 0; j < 100; j++) {
58 | System.out.println(Arrays.stream(error[j]).mapToObj(String::valueOf).collect(Collectors.joining("\t")));
59 | }
60 | }
61 |
62 | @Test
63 | public void testMany() {
64 | BloomVisitedSet set = new BloomVisitedSet(1 << 20, 2);
65 | int total = 1 << 16;
66 | for (int i = 0; i < 10; i++) {
67 | int failures = 0;
68 | for (int j = 0; j < total; j++) {
69 | Object obj = new Object();
70 | if (set.enter(obj) < 0)
71 | failures++;
72 | assertThat(set.enter(obj)).isLessThan(0);
73 | set.exit(obj, 0);
74 | }
75 | //System.out.println(failures / (double) total);
76 | assertThat(failures / (double) total).isLessThan(0.02);
77 | assertThat(set.softClear()).isFalse();
78 | }
79 | }
80 |
81 | @Test
82 | public void testAddingAgain() {
83 | BloomVisitedSet set = new BloomVisitedSet(1 << 20, 4);
84 |
85 | Object obj = new Object();
86 |
87 | assertThat(set.enter(obj)).isGreaterThanOrEqualTo(0);
88 | assertThat(set.enter(obj)).isLessThan(0);
89 | assertThat(set.enter(obj)).isLessThan(0);
90 | }
91 |
92 | }
93 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/ExpiringVisitedSetTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class ExpiringVisitedSetTest {
8 | @Test
9 | public void testMany() {
10 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 8);
11 | for (int j = 0; j < 1000; j++) {
12 | set.softClear();
13 | for (int i = 0; i < 16; i++) {
14 | Object obj = new Object();
15 | assertThat(set.enter(obj)).isGreaterThanOrEqualTo(0);
16 | assertThat(set.exit(obj, 0)).isTrue();
17 | }
18 | }
19 | }
20 |
21 | @Test
22 | public void testClear() {
23 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 8);
24 | Object obj = new Object();
25 | set.enter(obj);
26 | assertThat(set.contains(obj)).isNotNegative();
27 | set.clear();
28 | assertThat(set.contains(obj)).isNegative();
29 | }
30 |
31 | @Test
32 | public void testForceClear() {
33 | ExpiringVisitedSet set = new ExpiringVisitedSet(1 << 8, 1 << 20, 1 << 21);
34 |
35 | for (int i = 0; i < (1 << 12) - 2; i++) {
36 | assertThat(set.softClear()).isTrue();
37 | }
38 | assertThat(set.softClear()).isFalse();
39 | }
40 |
41 | @Test
42 | public void testExpireInTheMiddle() {
43 | ExpiringVisitedSet set = new ExpiringVisitedSet(2, 2, 8) {
44 | public int hash(Object obj) {
45 | return obj.hashCode();
46 | }
47 | };
48 |
49 | Custom c1 = new Custom(1);
50 | Custom c1b = new Custom(1);
51 | Custom c2 = new Custom(2);
52 |
53 | set.enter(c1);
54 | set.enter(c2);
55 | set.exit(c1, 0);
56 | set.exit(c2, 0);
57 |
58 | set.enter(c1b);
59 |
60 | //at this point, if [1] = c2, [2] = c1b, things are broken
61 | assertThat(set.contains(c1)).isLessThan(0);
62 | assertThat(set.contains(c1b)).isGreaterThanOrEqualTo(0);
63 | assertThat(set.contains(c2)).isGreaterThanOrEqualTo(0);
64 | }
65 |
66 | @Test
67 | public void testAvoidOverwrite() {
68 | ExpiringVisitedSet set = new ExpiringVisitedSet(4);
69 |
70 | Custom c1 = new Custom(1);
71 | Custom c2 = new Custom(2);
72 | Custom c1b = new Custom(1);
73 |
74 | set.enter(c1);
75 | set.enter(c2);
76 | set.enter(c1b);
77 |
78 | assertThat(set.contains(c1)).isGreaterThanOrEqualTo(0);
79 | assertThat(set.contains(c1b)).isGreaterThanOrEqualTo(0);
80 | assertThat(set.contains(c2)).isGreaterThanOrEqualTo(0);
81 | }
82 |
83 | @Test
84 | public void testAddingAgain() {
85 | ExpiringVisitedSet set = new ExpiringVisitedSet(16);
86 |
87 | Object obj = new Object();
88 |
89 | assertThat(set.contains(obj)).isLessThan(0);
90 | int index = set.enter(obj);
91 | assertThat(index).isGreaterThanOrEqualTo(0);
92 |
93 | assertThat(set.contains(obj)).isGreaterThanOrEqualTo(0);
94 | assertThat(set.enter(obj)).isEqualTo(~index);
95 |
96 | assertThat(set.contains(obj)).isGreaterThanOrEqualTo(0);
97 | assertThat(set.enter(obj)).isEqualTo(~index);
98 | }
99 |
100 | @Test
101 | public void testAddingPastEnd() {
102 | Object[] objs = new Object[16];
103 | ExpiringVisitedSet set = new ExpiringVisitedSet(objs.length);
104 |
105 | for (int i = 0; i < objs.length; i++) {
106 | objs[i] = new Object();
107 | assertThat(set.enter(objs[i])).isGreaterThanOrEqualTo(0);
108 | }
109 | for (int i = 0; i < objs.length; i++) {
110 | assertThat(set.contains(objs[i])).isGreaterThanOrEqualTo(0);
111 | }
112 | assertThat(set.enter(new Object())).isLessThan(0);
113 | set.softClear();
114 |
115 | for (int i = 0; i < objs.length; i++) {
116 | assertThat(set.contains(objs[i])).isLessThan(0);
117 | }
118 |
119 | for (int i = 0; i < objs.length; i++) {
120 | assertThat(set.enter(new Object())).isGreaterThanOrEqualTo(0);
121 | }
122 |
123 | assertThat(set.enter(new Object())).isLessThan(0);
124 | }
125 |
126 | @Test
127 | public void testExit() {
128 | Object[] objs = new Object[4];
129 | ExpiringVisitedSet set = new ExpiringVisitedSet(objs.length, objs.length, 4 * objs.length);
130 | set.softClear();
131 |
132 | for (int i = 0; i < objs.length; i++) {
133 | objs[i] = new Object();
134 | assertThat(set.enter(objs[i])).isGreaterThanOrEqualTo(0);
135 | assertThat(set.contains(objs[i])).isGreaterThanOrEqualTo(0);
136 | }
137 |
138 | assertThat(set.enter(new Object())).isLessThan(0);
139 | assertThat(set.exit(new Object(), 0)).isFalse();
140 |
141 | for (int i = 0; i < objs.length; i++) {
142 | assertThat(set.exit(objs[i], 0)).isTrue();
143 | }
144 |
145 | assertThat(set.enter(objs[0])).isLessThan(0); //object is still there, even with exit
146 | assertThat(set.enter(new Object())).isGreaterThanOrEqualTo(0); //forcing old object to be removed
147 | assertThat(set.enter(objs[0])).isGreaterThanOrEqualTo(0); //now object can be added again, and will remove #1
148 | assertThat(set.enter(objs[1])).isGreaterThanOrEqualTo(0); //#1 is removed and readded, now #2 is removed
149 | assertThat(set.enter(objs[3])).isLessThan(0); //but not #3 (yet)
150 | }
151 |
152 | private static class Custom {
153 | private final int hash;
154 |
155 | public Custom(int hash) {
156 | this.hash = hash;
157 | }
158 |
159 | @Override
160 | public int hashCode() {
161 | return hash;
162 | }
163 | }
164 |
165 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/IdentityVisitedSetTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.assertj.core.api.Assertions.assertThat;
6 |
7 | public class IdentityVisitedSetTest {
8 | @Test
9 | public void normalMethods() {
10 | IdentityVisitedSet set = new IdentityVisitedSet();
11 | Object o1 = new Object();
12 | Object o2 = new Object();
13 |
14 | assertThat(set.enter(o1)).isEqualTo(1);
15 | assertThat(set.enter(o1)).isEqualTo(-1);
16 |
17 | assertThat(set.exit(o1, -1)).isTrue();
18 | assertThat(set.exit(o2, -1)).isTrue();
19 |
20 | assertThat(set.enter(o1)).isEqualTo(-1);
21 | assertThat(set.enter(o2)).isEqualTo(1);
22 |
23 | assertThat(set.softClear()).isFalse();
24 |
25 | assertThat(set.enter(o1)).isEqualTo(1);
26 | assertThat(set.enter(o2)).isEqualTo(1);
27 |
28 | set.clear();
29 |
30 | assertThat(set.enter(o1)).isEqualTo(1);
31 | assertThat(set.enter(o2)).isEqualTo(1);
32 | }
33 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/PreconditionsTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import junit.framework.AssertionFailedError;
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.*;
7 |
8 | public class PreconditionsTest extends Preconditions {
9 | private static final String NON_NULL_STRING = "foo";
10 | private static final Object IGNORE_ME = new Object() {
11 | @Override
12 | public String toString() {
13 | throw new AssertionFailedError();
14 | }
15 | };
16 | private static final String FORMAT = "I ate %s pies.";
17 |
18 | private static void verifySimpleMessage(Exception e) {
19 | assertEquals("A message", e.getMessage());
20 | }
21 |
22 | private static void verifyComplexMessage(Exception e) {
23 | assertEquals("I ate 5 pies.", e.getMessage());
24 | }
25 |
26 | @Test
27 | public void testCheckArgument_simple_success() {
28 | Preconditions.checkArgument(true);
29 | }
30 |
31 | @Test
32 | public void testCheckArgument_simple_failure() {
33 | try {
34 | Preconditions.checkArgument(false);
35 | fail("no exception thrown");
36 | } catch (IllegalArgumentException expected) {
37 | }
38 | }
39 |
40 | @Test
41 | public void testCheckArgument_simpleMessage_success() {
42 | Preconditions.checkArgument(true, IGNORE_ME);
43 | }
44 |
45 | @Test
46 | public void testCheckArgument_simpleMessage_failure() {
47 | try {
48 | Preconditions.checkArgument(false, new Message());
49 | fail("no exception thrown");
50 | } catch (IllegalArgumentException expected) {
51 | verifySimpleMessage(expected);
52 | }
53 | }
54 |
55 | @Test
56 | public void testCheckArgument_nullMessage_failure() {
57 | try {
58 | Preconditions.checkArgument(false, null);
59 | fail("no exception thrown");
60 | } catch (IllegalArgumentException expected) {
61 | assertEquals("null", expected.getMessage());
62 | }
63 | }
64 |
65 | @Test
66 | public void testCheckArgument_complexMessage_success() {
67 | Preconditions.checkArgument(true, "%s", IGNORE_ME);
68 | }
69 |
70 | @Test
71 | public void testCheckArgument_complexMessage_failure() {
72 | try {
73 | Preconditions.checkArgument(false, FORMAT, 5);
74 | fail("no exception thrown");
75 | } catch (IllegalArgumentException expected) {
76 | verifyComplexMessage(expected);
77 | }
78 | }
79 |
80 | @Test
81 | public void testCheckState_simple_success() {
82 | Preconditions.checkState(true);
83 | }
84 |
85 | @Test
86 | public void testCheckState_simple_failure() {
87 | try {
88 | Preconditions.checkState(false);
89 | fail("no exception thrown");
90 | } catch (IllegalStateException expected) {
91 | }
92 | }
93 |
94 | @Test
95 | public void testCheckState_simpleMessage_success() {
96 | Preconditions.checkState(true, IGNORE_ME);
97 | }
98 |
99 | @Test
100 | public void testCheckState_simpleMessage_failure() {
101 | try {
102 | Preconditions.checkState(false, new Message());
103 | fail("no exception thrown");
104 | } catch (IllegalStateException expected) {
105 | verifySimpleMessage(expected);
106 | }
107 | }
108 |
109 | @Test
110 | public void testCheckState_nullMessage_failure() {
111 | try {
112 | Preconditions.checkState(false, null);
113 | fail("no exception thrown");
114 | } catch (IllegalStateException expected) {
115 | assertEquals("null", expected.getMessage());
116 | }
117 | }
118 |
119 | @Test
120 | public void testCheckState_complexMessage_success() {
121 | Preconditions.checkState(true, "%s", IGNORE_ME);
122 | }
123 |
124 | @Test
125 | public void testCheckState_complexMessage_failure() {
126 | try {
127 | Preconditions.checkState(false, FORMAT, 5);
128 | fail("no exception thrown");
129 | } catch (IllegalStateException expected) {
130 | verifyComplexMessage(expected);
131 | }
132 | }
133 |
134 | @Test
135 | public void testCheckNotNull_simple_success() {
136 | String result = Preconditions.checkNotNull(NON_NULL_STRING);
137 | assertSame(NON_NULL_STRING, result);
138 | }
139 |
140 | @Test
141 | public void testCheckNotNull_simple_failure() {
142 | try {
143 | Preconditions.checkNotNull(null);
144 | fail("no exception thrown");
145 | } catch (NullPointerException expected) {
146 | }
147 | }
148 |
149 | @Test
150 | public void testCheckNotNull_simpleMessage_success() {
151 | String result = Preconditions.checkNotNull(NON_NULL_STRING, IGNORE_ME);
152 | assertSame(NON_NULL_STRING, result);
153 | }
154 |
155 | @Test
156 | public void testCheckNotNull_simpleMessage_failure() {
157 | try {
158 | Preconditions.checkNotNull(null, new Message());
159 | fail("no exception thrown");
160 | } catch (NullPointerException expected) {
161 | verifySimpleMessage(expected);
162 | }
163 | }
164 |
165 | @Test
166 | public void testCheckNotNull_complexMessage_success() {
167 | String result = Preconditions.checkNotNull(
168 | NON_NULL_STRING, "%s", IGNORE_ME);
169 | assertSame(NON_NULL_STRING, result);
170 | }
171 |
172 | @Test
173 | public void testCheckNotNull_complexMessage_failure() {
174 | try {
175 | Preconditions.checkNotNull(null, FORMAT, 5);
176 | fail("no exception thrown");
177 | } catch (NullPointerException expected) {
178 | verifyComplexMessage(expected);
179 | }
180 | }
181 |
182 | @Test
183 | public void testCheckElementIndex_ok() {
184 | assertEquals(0, Preconditions.checkElementIndex(0, 1));
185 | assertEquals(0, Preconditions.checkElementIndex(0, 2));
186 | assertEquals(1, Preconditions.checkElementIndex(1, 2));
187 | }
188 |
189 | @Test
190 | public void testCheckElementIndex_badSize() {
191 | try {
192 | Preconditions.checkElementIndex(1, -1);
193 | fail();
194 | } catch (IllegalArgumentException expected) {
195 | // don't care what the message text is, as this is an invalid usage of
196 | // the Preconditions class, unlike all the other exceptions it throws
197 | }
198 | }
199 |
200 | @Test
201 | public void testCheckElementIndex_negative() {
202 | try {
203 | Preconditions.checkElementIndex(-1, 1);
204 | fail();
205 | } catch (IndexOutOfBoundsException expected) {
206 | assertEquals("index (-1) must not be negative", expected.getMessage());
207 | }
208 | }
209 |
210 | @Test
211 | public void testCheckElementIndex_tooHigh() {
212 | try {
213 | Preconditions.checkElementIndex(1, 1);
214 | fail();
215 | } catch (IndexOutOfBoundsException expected) {
216 | assertEquals("index (1) must be less than size (1)",
217 | expected.getMessage());
218 | }
219 | }
220 |
221 | @Test
222 | public void testCheckElementIndex_withDesc_negative() {
223 | try {
224 | Preconditions.checkElementIndex(-1, 1, "foo");
225 | fail();
226 | } catch (IndexOutOfBoundsException expected) {
227 | assertEquals("foo (-1) must not be negative", expected.getMessage());
228 | }
229 | }
230 |
231 | @Test
232 | public void testCheckElementIndex_withDesc_tooHigh() {
233 | try {
234 | Preconditions.checkElementIndex(1, 1, "foo");
235 | fail();
236 | } catch (IndexOutOfBoundsException expected) {
237 | assertEquals("foo (1) must be less than size (1)",
238 | expected.getMessage());
239 | }
240 | }
241 |
242 | @Test
243 | public void testCheckPositionIndex_ok() {
244 | assertEquals(0, Preconditions.checkPositionIndex(0, 0));
245 | assertEquals(0, Preconditions.checkPositionIndex(0, 1));
246 | assertEquals(1, Preconditions.checkPositionIndex(1, 1));
247 | }
248 |
249 | @Test
250 | public void testCheckPositionIndex_badSize() {
251 | try {
252 | Preconditions.checkPositionIndex(1, -1);
253 | fail();
254 | } catch (IllegalArgumentException expected) {
255 | // don't care what the message text is, as this is an invalid usage of
256 | // the Preconditions class, unlike all the other exceptions it throws
257 | }
258 | }
259 |
260 | @Test
261 | public void testCheckPositionIndex_negative() {
262 | try {
263 | Preconditions.checkPositionIndex(-1, 1);
264 | fail();
265 | } catch (IndexOutOfBoundsException expected) {
266 | assertEquals("index (-1) must not be negative", expected.getMessage());
267 | }
268 | }
269 |
270 | @Test
271 | public void testCheckPositionIndex_tooHigh() {
272 | try {
273 | Preconditions.checkPositionIndex(2, 1);
274 | fail();
275 | } catch (IndexOutOfBoundsException expected) {
276 | assertEquals("index (2) must not be greater than size (1)",
277 | expected.getMessage());
278 | }
279 | }
280 |
281 | @Test
282 | public void testCheckPositionIndex_withDesc_negative() {
283 | try {
284 | Preconditions.checkPositionIndex(-1, 1, "foo");
285 | fail();
286 | } catch (IndexOutOfBoundsException expected) {
287 | assertEquals("foo (-1) must not be negative", expected.getMessage());
288 | }
289 | }
290 |
291 | @Test
292 | public void testCheckPositionIndex_withDesc_tooHigh() {
293 | try {
294 | Preconditions.checkPositionIndex(2, 1, "foo");
295 | fail();
296 | } catch (IndexOutOfBoundsException expected) {
297 | assertEquals("foo (2) must not be greater than size (1)",
298 | expected.getMessage());
299 | }
300 | }
301 |
302 | @Test
303 | public void testCheckPositionIndexes_ok() {
304 | Preconditions.checkPositionIndexes(0, 0, 0);
305 | Preconditions.checkPositionIndexes(0, 0, 1);
306 | Preconditions.checkPositionIndexes(0, 1, 1);
307 | Preconditions.checkPositionIndexes(1, 1, 1);
308 | }
309 |
310 | @Test
311 | public void testCheckPositionIndexes_badSize() {
312 | try {
313 | Preconditions.checkPositionIndexes(1, 1, -1);
314 | fail();
315 | } catch (IllegalArgumentException expected) {
316 | }
317 | }
318 |
319 | @Test
320 | public void testCheckPositionIndex_startNegative() {
321 | try {
322 | Preconditions.checkPositionIndexes(-1, 1, 1);
323 | fail();
324 | } catch (IndexOutOfBoundsException expected) {
325 | assertEquals("start index (-1) must not be negative",
326 | expected.getMessage());
327 | }
328 | }
329 |
330 | @Test
331 | public void testCheckPositionIndexes_endTooHigh() {
332 | try {
333 | Preconditions.checkPositionIndexes(0, 2, 1);
334 | fail();
335 | } catch (IndexOutOfBoundsException expected) {
336 | assertEquals("end index (2) must not be greater than size (1)",
337 | expected.getMessage());
338 | }
339 | }
340 |
341 | @Test
342 | public void testCheckPositionIndexes_reversed() {
343 | try {
344 | Preconditions.checkPositionIndexes(1, 0, 1);
345 | fail();
346 | } catch (IndexOutOfBoundsException expected) {
347 | assertEquals("end index (0) must not be less than start index (1)",
348 | expected.getMessage());
349 | }
350 | }
351 |
352 | @Test
353 | public void testFormat() {
354 | assertEquals("%s", Preconditions.format("%s"));
355 | assertEquals("5", Preconditions.format("%s", 5));
356 | assertEquals("foo [5]", Preconditions.format("foo", 5));
357 | assertEquals("foo [5, 6, 7]", Preconditions.format("foo", 5, 6, 7));
358 | assertEquals("%s 1 2", Preconditions.format("%s %s %s", "%s", 1, 2));
359 | assertEquals(" [5, 6]", Preconditions.format("", 5, 6));
360 | assertEquals("123", Preconditions.format("%s%s%s", 1, 2, 3));
361 | assertEquals("1%s%s", Preconditions.format("%s%s%s", 1));
362 | assertEquals("5 + 6 = 11", Preconditions.format("%s + 6 = 11", 5));
363 | assertEquals("5 + 6 = 11", Preconditions.format("5 + %s = 11", 6));
364 | assertEquals("5 + 6 = 11", Preconditions.format("5 + 6 = %s", 11));
365 | assertEquals("5 + 6 = 11", Preconditions.format("%s + %s = %s", 5, 6, 11));
366 | assertEquals("null [null, null]",
367 | Preconditions.format("%s", null, null, null));
368 | assertEquals("null [5, 6]", Preconditions.format(null, 5, 6));
369 | }
370 |
371 | private static class Message {
372 | boolean invoked;
373 |
374 | @Override
375 | public String toString() {
376 | assertFalse(invoked);
377 | invoked = true;
378 | return "A message";
379 | }
380 | }
381 | }
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/SuppressForbidden.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.CLASS)
9 | @Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
10 | public @interface SuppressForbidden {
11 | }
12 |
--------------------------------------------------------------------------------
/src/test/java/net/intelie/introspective/util/UnsafeGetterTest.java:
--------------------------------------------------------------------------------
1 | package net.intelie.introspective.util;
2 |
3 | import org.junit.Test;
4 | import sun.misc.Unsafe;
5 |
6 | import static org.assertj.core.api.Assertions.assertThat;
7 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
8 |
9 | public class UnsafeGetterTest {
10 | @Test
11 | public void testCouldGetUnsafe() {
12 | assertThat(UnsafeGetter.get()).isNotNull();
13 | }
14 |
15 | @Test
16 | public void testCouldGetInternalObjectFieldOffsetMethodHandle() {
17 | assertThat(UnsafeGetter.getObjectFieldOffset()).isNotNull();
18 | }
19 |
20 | @Test
21 | public void testExceptionHandling() {
22 | assertThatThrownBy(() -> UnsafeGetter.tryGetAccessible(Unsafe.class, "unknown"))
23 | .isInstanceOf(IllegalStateException.class);
24 | }
25 |
26 | @Test
27 | public void testExceptionHandlingInternalUnsafe() {
28 | assertThatThrownBy(() -> UnsafeGetter.tryGetRealMethodHandle(Unsafe.class, "unknown", "unknown", "objectFieldOffset"))
29 | .isInstanceOf(RuntimeException.class).hasCauseInstanceOf(NoSuchFieldException.class);
30 | }
31 |
32 | @Test
33 | public void testExceptionHandlingInternalMethod() {
34 | assertThatThrownBy(() -> UnsafeGetter.tryGetRealMethodHandle(Unsafe.class, "theInternalUnsafe", "theUnsafe", "unknown"))
35 | .isInstanceOf(RuntimeException.class).hasCauseInstanceOf(NoSuchMethodException.class);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/test/java/org/openjdk/jol/vm/LightVM.java:
--------------------------------------------------------------------------------
1 | package org.openjdk.jol.vm;
2 |
3 | import sun.misc.Unsafe;
4 |
5 | import java.lang.reflect.Field;
6 | import java.security.AccessController;
7 | import java.security.PrivilegedAction;
8 |
9 | public class LightVM extends HotspotUnsafe {
10 | private static Unsafe tryUnsafe() {
11 | return AccessController.doPrivileged(
12 | (PrivilegedAction) () -> {
13 | try {
14 | Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");
15 | unsafe.setAccessible(true);
16 | return (Unsafe) unsafe.get(null);
17 | } catch (NoSuchFieldException | IllegalAccessException e) {
18 | throw new IllegalStateException(e);
19 | }
20 | }
21 | );
22 | }
23 |
24 | private static LightVM INSTANCE;
25 |
26 | public static LightVM current() {
27 | String name = System.getProperty("java.vm.name");
28 | if (!name.contains("HotSpot") && !name.contains("OpenJDK")) {
29 | throw new IllegalStateException("Only HotSpot/OpenJDK VMs are supported");
30 | }
31 |
32 | Unsafe u = tryUnsafe();
33 | if (u == null) {
34 | throw new IllegalStateException("Unsafe is not available.");
35 | }
36 |
37 | if (INSTANCE == null) {
38 | LightVM vm = new LightVM(u);
39 | try {
40 | Field field = VM.class.getDeclaredField("INSTANCE");
41 | field.setAccessible(true);
42 | field.set(null, vm);
43 | } catch (IllegalAccessException | NoSuchFieldException e) {
44 | throw new IllegalStateException(e);
45 | }
46 | INSTANCE = vm;
47 | }
48 | return INSTANCE;
49 | }
50 |
51 | public boolean isCompressedOopsEnabled() {
52 | return VMOptions.pollCompressedOops();
53 | }
54 |
55 | LightVM(Unsafe u) {
56 | super(u, null);
57 | }
58 |
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/test:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | testwith() {
3 | echo Testing in $1
4 |
5 | docker run -it --rm \
6 | --mount "type=bind,source=$HOME/.m2,destination=/root/.m2,readonly" \
7 | --mount "type=bind,source=$(pwd),destination=/root/source,readonly" \
8 | -w /root/work \
9 | $1 \
10 | /bin/bash -c "cp -r /root/source/* /root/work/ && mvn -q clean test"
11 | }
12 |
13 | set -e
14 | testwith maven:3-jdk-8
15 | testwith maven:3-jdk-9
16 | testwith maven:3-jdk-10
17 | testwith maven:3-jdk-11
18 | testwith maven:3-jdk-12
19 | testwith maven:3-jdk-13
20 | testwith maven:3-jdk-14
21 | testwith maven:3-openjdk-15
22 | testwith maven:3-openjdk-16
23 | testwith maven:3-openjdk-17
24 | testwith maven:3-amazoncorretto-8
25 | testwith maven:3-amazoncorretto-11
26 | testwith maven:3-amazoncorretto-17
27 |
28 |
--------------------------------------------------------------------------------