1 /* 2 * Copyright (c) 2022, Red Hat, Inc. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package net.shipilev; 27 28 import java.io.*; 29 import java.lang.reflect.Field; 30 import java.util.function.ToLongFunction; 31 import java.util.Arrays; 32 import java.util.ArrayDeque; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.Optional; 36 37 import jdk.internal.vm.annotation.IntrinsicCandidate; 38 import jdk.internal.vm.annotation.DontInline; 39 import jdk.internal.vm.annotation.IntrinsicCandidate; 40 41 public class Magic { 42 43 /* 44 * ---------------------- INTERFACE -------------------------- 45 * (If you are lazy to look into Javadoc) 46 * ----------------------------------------------------------- 47 */ 48 49 /** 50 * Returns the current CPU timestamp counter. 51 * <p> 52 * Maps to RDTSC on X86. No other platform is supported (yet). 53 * 54 * @return TSC value, or -1 if not supported 55 */ 56 @IntrinsicCandidate 57 public static native long timestamp(); 58 59 /** 60 * Same as {@link #timestamp}, but also provides instruction 61 * stream serialization. May be more expensive. 62 * <p> 63 * Maps to RDTSCP on X86. No other platform is supported (yet). 64 * 65 * @return TSC value, or -1 if not supported 66 */ 67 @IntrinsicCandidate 68 public static native long timestampSerial(); 69 70 /** 71 * Returns the implementation-specific estimate of the amount of storage 72 * consumed by the specified object. 73 * <p> 74 * The estimate may change during a single invocation of the JVM. 75 * 76 * @param obj object to estimate the size of 77 * @return storage size in bytes 78 * @throws NullPointerException if {@code obj} is {@code null} 79 * @since 16 80 */ 81 @DontInline // Semantics: make sure the object is not scalar replaced. 82 public static long sizeOf(Object obj) { 83 Objects.requireNonNull(obj); 84 return sizeOf0(obj); 85 } 86 87 /** 88 * Returns the implementation-specific estimate of the offset of the field 89 * within the holding container. 90 * <p> 91 * For the instance fields, the offset is from the beginning of the holder 92 * object. For the static fields, the offset is from the beginning of the 93 * unspecified holder area. As such, these offsets are useful for comparing 94 * the offsets of two fields, not for any kind of absolute addressing. 95 * <p> 96 * The estimate may change during a single invocation of the JVM, for example 97 * during class redefinition. 98 * 99 * @param field field to poll 100 * @return the field offset in bytes 101 * @throws NullPointerException if {@code field} is {@code null} 102 * @since 16 103 */ 104 public static long fieldOffsetOf(Field field) { 105 Objects.requireNonNull(field); 106 return fieldOffsetOf0(field); 107 } 108 109 /** 110 * Returns the implementation-specific estimate of the field slot size for 111 * the specified object field. 112 * <p> 113 * The estimate may change during a single invocation of the JVM. 114 * 115 * TODO: Split by staticness? 116 * 117 * @param field field to poll 118 * @return the field size in bytes 119 * @throws NullPointerException if {@code field} is {@code null} 120 * @since 16 121 */ 122 public static long fieldSizeOf(Field field) { 123 Objects.requireNonNull(field); 124 return fieldSizeOf0(field); 125 } 126 127 /** 128 * Returns the implementation-specific estimate of the amount of storage 129 * consumed by the specified object and all objects referenced by it. 130 * <p> 131 * The estimate may change during a single invocation of the JVM. Notably, 132 * the estimate is not guaranteed to remain stable if the object references in 133 * the walked subgraph change when {@code deepSizeOf} is running. 134 * 135 * @param obj root object to start the estimate from 136 * @return storage size in bytes 137 * @throws NullPointerException if {@code obj} is {@code null} 138 * @since 16 139 */ 140 public static long deepSizeOf(Object obj) { 141 return deepSizeOf0(obj, (o) -> DEEP_SIZE_OF_TRAVERSE | DEEP_SIZE_OF_SHALLOW); 142 } 143 144 145 /** 146 * Returns the implementation-specific estimate of the amount of storage 147 * consumed by the specified object and all objects referenced by it. 148 * <p> 149 * The estimate may change during a single invocation of the JVM. Notably, 150 * the estimate is not guaranteed to remain stable if the object references in 151 * the walked subgraph change when {@code deepSizeOf} is running. 152 * 153 * @param obj root object to start the estimate from 154 * @param includeCheck callback to evaluate an object's size. The callback can 155 * return a positive value as a bitmask - valid values are 156 * {@link #DEEP_SIZE_OF_SHALLOW} to consider the object's shallow sise and 157 * {@link #DEEP_SIZE_OF_TRAVERSE} to traverse ("go deeper") the object's 158 * references. A negative value means that the absolute return value is 159 * considered and the object's references are not considered. 160 * @return storage size in bytes 161 * @throws NullPointerException if {@code obj} is {@code null} 162 * @since 16 163 */ 164 public static long deepSizeOf(Object obj, ToLongFunction<Object> includeCheck) { 165 return deepSizeOf0(obj, includeCheck); 166 } 167 168 /** 169 * Returns the implementation-specific representation of the memory address 170 * where the specified object resides. 171 * <p> 172 * The estimate may change during a single invocation of the JVM. Notably, 173 * in the presence of moving garbage collector, the address can change at any 174 * time, including during the call. As such, this method is only useful for 175 * low-level debugging and heap introspection in a quiescent application. 176 * <p> 177 * The JVM is also free to provide non-verbatim answers, for example, adding 178 * the random offset in order to hide the real object addresses. Because of this, 179 * this method is useful to compare the relative Object positions, or inspecting 180 * the object alignments up to some high threshold, but not for accessing the objects 181 * via the naked native address. 182 * 183 * @param obj object to get the address of 184 * @return current object address 185 * @since 16 186 */ 187 @DontInline // Semantics: make sure the object is not scalar replaced. 188 public static long addressOf(Object obj) { 189 Objects.requireNonNull(obj); 190 return addressOf0(obj); 191 } 192 193 /* 194 * ---------------------- IMPLEMENTATION -------------------------- 195 */ 196 197 private static native void registerNatives(); 198 static { 199 registerNatives(); 200 } 201 202 public Magic() { 203 throw new IllegalArgumentException("NO INSTANCE MAGIC FOR YOU."); 204 } 205 206 /** 207 * Bit value for {@link #deepSizeOf(Object, ToLongFunction)}'s callback 208 * return value to continue traversal ("go deep") of the references of 209 * the object passed to the callback. 210 */ 211 public static final long DEEP_SIZE_OF_TRAVERSE = 1; 212 /** 213 * Bit value for {@link #deepSizeOf(Object, ToLongFunction)}'s callback 214 * return value to consider the shallow size of the object passed to the 215 * callback. 216 */ 217 public static final long DEEP_SIZE_OF_SHALLOW = 2; 218 219 private static long handleIncludeCheck(ArrayDeque<Object> q, Object o, ToLongFunction<Object> ic, long ts, long os) { 220 long t = ic.applyAsLong(o); 221 if (t > 0) { 222 if ((t & DEEP_SIZE_OF_TRAVERSE) != 0) { 223 q.push(o); 224 } 225 if ((t & DEEP_SIZE_OF_SHALLOW) != 0) { 226 ts += os; 227 } 228 } else { 229 ts -= t; 230 } 231 return ts; 232 } 233 234 @DontInline // Semantics: make sure the object is not scalar replaced. 235 private static long deepSizeOf0(Object obj, ToLongFunction<Object> includeCheck) { 236 Objects.requireNonNull(obj); 237 238 IdentityHashSet visited = new IdentityHashSet(IdentityHashSet.MINIMUM_CAPACITY); 239 ArrayDeque<Object> q = new ArrayDeque<>(); 240 241 visited.add(obj); 242 243 long rootSize = sizeOf0(obj); 244 long totalSize = handleIncludeCheck(q, obj, includeCheck, 0, rootSize); 245 246 Object[] refBuf = new Object[1]; 247 248 while (!q.isEmpty()) { 249 Object o = q.pop(); 250 Class<?> cl = o.getClass(); 251 if (cl.isArray()) { 252 // Separate array path avoids adding a lot of (potentially large) array 253 // contents on the queue. No need to handle primitive arrays too. 254 255 if (cl.getComponentType().isPrimitive()) { 256 continue; 257 } 258 259 for (Object e : (Object[])o) { 260 if (e != null && visited.add(e)) { 261 long size = sizeOf0(e); 262 totalSize = handleIncludeCheck(q, e, includeCheck, totalSize, size); 263 } 264 } 265 } else { 266 int objs; 267 while ((objs = getReferencedObjects(o, refBuf)) < 0) { 268 refBuf = new Object[refBuf.length * 2]; 269 } 270 271 for (int c = 0; c < objs; c++) { 272 Object e = refBuf[c]; 273 if (visited.add(e)) { 274 long size = sizeOf0(e); 275 totalSize = handleIncludeCheck(q, e, includeCheck, totalSize, size); 276 } 277 } 278 279 // Null out the buffer: do not keep these objects referenced until next 280 // buffer fill, and help the VM code to avoid full SATB barriers on existing 281 // buffer elements in getReferencedObjects. 282 Arrays.fill(refBuf, 0, objs, null); 283 } 284 } 285 286 return totalSize; 287 } 288 289 /** 290 * Peels the referenced objects from the given object and puts them 291 * into the reference buffer. Never returns nulls in reference buffer. 292 * Returns the number of valid elements in the buffer. If reference bufffer 293 * is too small, returns -1. 294 * 295 * @param obj object to peel 296 * @param refBuf reference buffer 297 * @return number of valid elements in buffer, -1 if buffer is too small 298 */ 299 @IntrinsicCandidate 300 private static native int getReferencedObjects(Object obj, Object[] refBuf); 301 302 private static final class IdentityHashSet { 303 private static final int MINIMUM_CAPACITY = 4; 304 private static final int MAXIMUM_CAPACITY = 1 << 29; 305 306 private Object[] table; 307 private int size; 308 309 public IdentityHashSet(int expectedMaxSize) { 310 table = new Object[capacity(expectedMaxSize)]; 311 } 312 313 private static int capacity(int expectedMaxSize) { 314 return 315 (expectedMaxSize > MAXIMUM_CAPACITY / 3) ? MAXIMUM_CAPACITY : 316 (expectedMaxSize <= 2 * MINIMUM_CAPACITY / 3) ? MINIMUM_CAPACITY : 317 Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1)); 318 } 319 320 private static int nextIndex(int i, int len) { 321 return (i + 1 < len ? i + 1 : 0); 322 } 323 324 public boolean add(Object o) { 325 while (true) { 326 final Object[] tab = table; 327 final int len = tab.length; 328 int i = System.identityHashCode(o) & (len - 1); 329 330 for (Object item; (item = tab[i]) != null; i = nextIndex(i, len)) { 331 if (item == o) { 332 return false; 333 } 334 } 335 336 final int s = size + 1; 337 if (s*3 > len && resize()) continue; 338 339 tab[i] = o; 340 size = s; 341 return true; 342 } 343 } 344 345 private boolean resize() { 346 Object[] oldTable = table; 347 int oldLength = oldTable.length; 348 if (oldLength == MAXIMUM_CAPACITY) { 349 throw new IllegalStateException("Capacity exhausted."); 350 } 351 352 int newLength = oldLength * 2; 353 if (newLength <= oldLength) { 354 return false; 355 } 356 357 Object[] newTable = new Object[newLength]; 358 for (Object o : oldTable) { 359 if (o != null) { 360 int i = System.identityHashCode(o) & (newLength - 1); 361 while (newTable[i] != null) { 362 i = nextIndex(i, newLength); 363 } 364 newTable[i] = o; 365 } 366 } 367 table = newTable; 368 return true; 369 } 370 } 371 372 @IntrinsicCandidate 373 private static native long sizeOf0(Object obj); 374 375 @IntrinsicCandidate 376 private static native long addressOf0(Object obj); 377 378 // Reflection-like call, is not supposed to be fast? 379 private static native long fieldOffsetOf0(Field field); 380 381 // Reflection-like call, is not supposed to be fast? 382 private static native long fieldSizeOf0(Field field); 383 384 }