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 }