├── .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 | --------------------------------------------------------------------------------