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 }