1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. 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 jdk.nashorn.internal.ir.debug;
27
28 import java.lang.reflect.Array;
29 import java.lang.reflect.Field;
30 import java.lang.reflect.InvocationTargetException;
31 import java.lang.reflect.Method;
32 import java.lang.reflect.Modifier;
33 import java.util.ArrayDeque;
34 import java.util.ArrayList;
35 import java.util.Arrays;
36 import java.util.Deque;
37 import java.util.IdentityHashMap;
38 import java.util.LinkedList;
39 import java.util.List;
40 import java.util.Map;
41
42 /**
43 * Contains utility methods for calculating the memory usage of objects. It
44 * only works on the HotSpot JVM, and infers the actual memory layout (32 bit
45 * vs. 64 bit word size, compressed object pointers vs. uncompressed) from
46 * best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM.
47 * It can only make an educated guess at whether compressed OOPs are used,
48 * though; specifically, it knows what the JVM's default choice of OOP
49 * compression would be based on HotSpot version and maximum heap sizes, but if
50 * the choice is explicitly overridden with the <tt>-XX:{+|-}UseCompressedOops</tt> command line
51 * switch, it can not detect
52 * this fact and will report incorrect sizes, as it will presume the default JVM
53 * behavior.
54 */
55 public final class ObjectSizeCalculator {
56
57 /**
58 * Describes constant memory overheads for various constructs in a JVM implementation.
59 */
60 public interface MemoryLayoutSpecification {
61
62 /**
63 * Returns the fixed overhead of an array of any type or length in this JVM.
64 *
65 * @return the fixed overhead of an array.
66 */
67 int getArrayHeaderSize();
68
69 /**
70 * Returns the fixed overhead of for any {@link Object} subclass in this JVM.
71 *
72 * @return the fixed overhead of any object.
73 */
74 int getObjectHeaderSize();
75
76 /**
77 * Returns the quantum field size for a field owned by an object in this JVM.
78 *
79 * @return the quantum field size for an object.
80 */
81 int getObjectPadding();
82
83 /**
84 * Returns the fixed size of an object reference in this JVM.
85 *
86 * @return the size of all object references.
87 */
88 int getReferenceSize();
89
90 /**
91 * Returns the quantum field size for a field owned by one of an object's ancestor superclasses
92 * in this JVM.
93 *
94 * @return the quantum field size for a superclass field.
95 */
96 int getSuperclassFieldPadding();
97 }
98
99 private static class CurrentLayout {
100 private static final MemoryLayoutSpecification SPEC =
101 getEffectiveMemoryLayoutSpecification();
102 }
103
104 /**
105 * Given an object, returns the total allocated size, in bytes, of the object
106 * and all other objects reachable from it. Attempts to to detect the current JVM memory layout,
107 * but may fail with {@link UnsupportedOperationException};
108 *
109 * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do
110 * anything special, it measures the size of all objects
111 * reachable through it (which will include its class loader, and by
112 * extension, all other Class objects loaded by
113 * the same loader, and all the parent class loaders). It doesn't provide the
114 * size of the static fields in the JVM class that the Class object
115 * represents.
116 * @return the total allocated size of the object and all other objects it
117 * retains.
118 * @throws UnsupportedOperationException if the current vm memory layout cannot be detected.
119 */
120 public static long getObjectSize(final Object obj) throws UnsupportedOperationException {
121 return obj == null ? 0 : new ObjectSizeCalculator(CurrentLayout.SPEC).calculateObjectSize(obj);
122 }
123
124 // Fixed object header size for arrays.
125 private final int arrayHeaderSize;
126 // Fixed object header size for non-array objects.
127 private final int objectHeaderSize;
128 // Padding for the object size - if the object size is not an exact multiple
129 // of this, it is padded to the next multiple.
130 private final int objectPadding;
131 // Size of reference (pointer) fields.
132 private final int referenceSize;
133 // Padding for the fields of superclass before fields of subclasses are
134 // added.
135 private final int superclassFieldPadding;
136
137 private final Map<Class<?>, ClassSizeInfo> classSizeInfos = new IdentityHashMap<>();
138
139
140 private final Map<Object, Object> alreadyVisited = new IdentityHashMap<>();
141 private final Map<Class<?>, ClassHistogramElement> histogram = new IdentityHashMap<>();
142
143 private final Deque<Object> pending = new ArrayDeque<>(16 * 1024);
144 private long size;
145
146 /**
147 * Creates an object size calculator that can calculate object sizes for a given
148 * {@code memoryLayoutSpecification}.
149 *
150 * @param memoryLayoutSpecification a description of the JVM memory layout.
151 */
152 public ObjectSizeCalculator(final MemoryLayoutSpecification memoryLayoutSpecification) {
153 memoryLayoutSpecification.getClass();
154 arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize();
155 objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize();
156 objectPadding = memoryLayoutSpecification.getObjectPadding();
157 referenceSize = memoryLayoutSpecification.getReferenceSize();
158 superclassFieldPadding = memoryLayoutSpecification.getSuperclassFieldPadding();
159 }
160
161 /**
162 * Given an object, returns the total allocated size, in bytes, of the object
163 * and all other objects reachable from it.
164 *
165 * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do
166 * anything special, it measures the size of all objects
167 * reachable through it (which will include its class loader, and by
168 * extension, all other Class objects loaded by
169 * the same loader, and all the parent class loaders). It doesn't provide the
170 * size of the static fields in the JVM class that the Class object
171 * represents.
172 * @return the total allocated size of the object and all other objects it
173 * retains.
174 */
175 public synchronized long calculateObjectSize(final Object obj) {
176 // Breadth-first traversal instead of naive depth-first with recursive
177 // implementation, so we don't blow the stack traversing long linked lists.
178 histogram.clear();
179 try {
180 for (Object o = obj;;) {
181 visit(o);
182 if (pending.isEmpty()) {
183 return size;
184 }
185 o = pending.removeFirst();
186 }
187 } finally {
188 alreadyVisited.clear();
189 pending.clear();
190 size = 0;
191 }
192 }
193
194 /**
195 * Get the class histograpm
196 * @return class histogram element list
197 */
198 public List<ClassHistogramElement> getClassHistogram() {
199 return new ArrayList<>(histogram.values());
200 }
201
202 private ClassSizeInfo getClassSizeInfo(final Class<?> clazz) {
203 ClassSizeInfo csi = classSizeInfos.get(clazz);
204 if(csi == null) {
205 csi = new ClassSizeInfo(clazz);
206 classSizeInfos.put(clazz, csi);
207 }
208 return csi;
209 }
210
211 private void visit(final Object obj) {
212 if (alreadyVisited.containsKey(obj)) {
213 return;
214 }
215 final Class<?> clazz = obj.getClass();
216 if (clazz == ArrayElementsVisitor.class) {
217 ((ArrayElementsVisitor) obj).visit(this);
218 } else {
219 alreadyVisited.put(obj, obj);
220 if (clazz.isArray()) {
221 visitArray(obj);
222 } else {
223 getClassSizeInfo(clazz).visit(obj, this);
224 }
225 }
226 }
227
228 private void visitArray(final Object array) {
229 final Class<?> arrayClass = array.getClass();
230 final Class<?> componentType = arrayClass.getComponentType();
231 final int length = Array.getLength(array);
232 if (componentType.isPrimitive()) {
233 increaseByArraySize(arrayClass, length, getPrimitiveFieldSize(componentType));
234 } else {
235 increaseByArraySize(arrayClass, length, referenceSize);
236 // If we didn't use an ArrayElementsVisitor, we would be enqueueing every
237 // element of the array here instead. For large arrays, it would
238 // tremendously enlarge the queue. In essence, we're compressing it into
239 // a small command object instead. This is different than immediately
240 // visiting the elements, as their visiting is scheduled for the end of
241 // the current queue.
242 switch (length) {
243 case 0: {
244 break;
245 }
246 case 1: {
247 enqueue(Array.get(array, 0));
248 break;
249 }
250 default: {
251 enqueue(new ArrayElementsVisitor((Object[]) array));
252 }
253 }
254 }
255 }
256
257 private void increaseByArraySize(final Class<?> clazz, final int length, final long elementSize) {
258 increaseSize(clazz, roundTo(arrayHeaderSize + length * elementSize, objectPadding));
259 }
260
261 private static class ArrayElementsVisitor {
262 private final Object[] array;
263
264 ArrayElementsVisitor(final Object[] array) {
265 this.array = array;
266 }
267
268 public void visit(final ObjectSizeCalculator calc) {
269 for (final Object elem : array) {
270 if (elem != null) {
271 calc.visit(elem);
272 }
273 }
274 }
275 }
276
277 void enqueue(final Object obj) {
278 if (obj != null) {
279 pending.addLast(obj);
280 }
281 }
282
283 void increaseSize(final Class<?> clazz, final long objectSize) {
284 ClassHistogramElement he = histogram.get(clazz);
285 if(he == null) {
286 he = new ClassHistogramElement(clazz);
287 histogram.put(clazz, he);
288 }
289 he.addInstance(objectSize);
290 size += objectSize;
291 }
292
293 static long roundTo(final long x, final int multiple) {
294 return ((x + multiple - 1) / multiple) * multiple;
295 }
296
297 private class ClassSizeInfo {
298 // Padded fields + header size
299 private final long objectSize;
300 // Only the fields size - used to calculate the subclasses' memory
301 // footprint.
302 private final long fieldsSize;
303 private final Field[] referenceFields;
304
305 public ClassSizeInfo(final Class<?> clazz) {
306 long newFieldsSize = 0;
307 final List<Field> newReferenceFields = new LinkedList<>();
308 for (final Field f : clazz.getDeclaredFields()) {
309 if (Modifier.isStatic(f.getModifiers())) {
310 continue;
311 }
312 final Class<?> type = f.getType();
313 if (type.isPrimitive()) {
314 newFieldsSize += getPrimitiveFieldSize(type);
315 } else {
316 f.setAccessible(true);
317 newReferenceFields.add(f);
318 newFieldsSize += referenceSize;
319 }
320 }
321 final Class<?> superClass = clazz.getSuperclass();
322 if (superClass != null) {
323 final ClassSizeInfo superClassInfo = getClassSizeInfo(superClass);
324 newFieldsSize += roundTo(superClassInfo.fieldsSize, superclassFieldPadding);
325 newReferenceFields.addAll(Arrays.asList(superClassInfo.referenceFields));
326 }
327 this.fieldsSize = newFieldsSize;
328 this.objectSize = roundTo(objectHeaderSize + newFieldsSize, objectPadding);
329 this.referenceFields = newReferenceFields.toArray(
330 new Field[newReferenceFields.size()]);
331 }
332
333 void visit(final Object obj, final ObjectSizeCalculator calc) {
334 calc.increaseSize(obj.getClass(), objectSize);
335 enqueueReferencedObjects(obj, calc);
336 }
337
338 public void enqueueReferencedObjects(final Object obj, final ObjectSizeCalculator calc) {
339 for (final Field f : referenceFields) {
340 try {
341 calc.enqueue(f.get(obj));
342 } catch (final IllegalAccessException e) {
343 final AssertionError ae = new AssertionError(
344 "Unexpected denial of access to " + f);
345 ae.initCause(e);
346 throw ae;
347 }
348 }
349 }
350 }
351
352 private static long getPrimitiveFieldSize(final Class<?> type) {
353 if (type == boolean.class || type == byte.class) {
354 return 1;
355 }
356 if (type == char.class || type == short.class) {
357 return 2;
358 }
359 if (type == int.class || type == float.class) {
360 return 4;
361 }
362 if (type == long.class || type == double.class) {
363 return 8;
364 }
365 throw new AssertionError("Encountered unexpected primitive type " +
366 type.getName());
367 }
368
369 // ALERT: java.lang.management is not available in compact 1. We need
370 // to use reflection to soft link test memory statistics.
371
372 static Class<?> managementFactory = null;
373 static Class<?> memoryPoolMXBean = null;
374 static Class<?> memoryUsage = null;
375 static Method getMemoryPoolMXBeans = null;
376 static Method getUsage = null;
377 static Method getMax = null;
378 static {
379 try {
380 managementFactory = Class.forName("java.lang.management.ManagementFactory");
381 memoryPoolMXBean = Class.forName("java.lang.management.MemoryPoolMXBean");
382 memoryUsage = Class.forName("java.lang.management.MemoryUsage");
383
384 getMemoryPoolMXBeans = managementFactory.getMethod("getMemoryPoolMXBeans");
385 getUsage = memoryPoolMXBean.getMethod("getUsage");
386 getMax = memoryUsage.getMethod("getMax");
387 } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) {
388 // Pass thru, asserts when attempting to use.
389 }
390 }
391
392 /**
393 * Return the current memory usage
394 * @return current memory usage derived from system configuration
395 */
396 public static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() {
397 final String vmName = System.getProperty("java.vm.name");
398 if (vmName == null || !vmName.startsWith("Java HotSpot(TM) ")) {
399 throw new UnsupportedOperationException(
400 "ObjectSizeCalculator only supported on HotSpot VM");
401 }
402
403 final String dataModel = System.getProperty("sun.arch.data.model");
404 if ("32".equals(dataModel)) {
405 // Running with 32-bit data model
406 return new MemoryLayoutSpecification() {
407 @Override public int getArrayHeaderSize() {
408 return 12;
409 }
410 @Override public int getObjectHeaderSize() {
411 return 8;
412 }
413 @Override public int getObjectPadding() {
414 return 8;
415 }
416 @Override public int getReferenceSize() {
417 return 4;
418 }
419 @Override public int getSuperclassFieldPadding() {
420 return 4;
421 }
422 };
423 } else if (!"64".equals(dataModel)) {
424 throw new UnsupportedOperationException("Unrecognized value '" +
425 dataModel + "' of sun.arch.data.model system property");
426 }
427
428 final String strVmVersion = System.getProperty("java.vm.version");
429 final int vmVersion = Integer.parseInt(strVmVersion.substring(0,
430 strVmVersion.indexOf('.')));
431 if (vmVersion >= 17) {
432 long maxMemory = 0;
433
434 /*
435 See ALERT above. The reflection code below duplicates the following
436 sequence, and avoids hard coding of java.lang.management.
437
438 for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
439 maxMemory += mp.getUsage().getMax();
440 }
441 */
442
443 if (getMemoryPoolMXBeans == null) {
444 throw new AssertionError("java.lang.management not available in compact 1");
445 }
446
447 try {
448 final List<?> memoryPoolMXBeans = (List<?>)getMemoryPoolMXBeans.invoke(managementFactory);
449 for (final Object mp : memoryPoolMXBeans) {
450 final Object usage = getUsage.invoke(mp);
451 final Object max = getMax.invoke(usage);
452 maxMemory += ((Long)max).longValue();
453 }
454 } catch (IllegalAccessException |
455 IllegalArgumentException |
456 InvocationTargetException ex) {
457 throw new AssertionError("java.lang.management not available in compact 1");
458 }
459
460 if (maxMemory < 30L * 1024 * 1024 * 1024) {
461 // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM total
462 // for all memory pools (yes, including code cache).
463 return new MemoryLayoutSpecification() {
464 @Override public int getArrayHeaderSize() {
465 return 16;
466 }
467 @Override public int getObjectHeaderSize() {
468 return 12;
469 }
470 @Override public int getObjectPadding() {
471 return 8;
472 }
473 @Override public int getReferenceSize() {
474 return 4;
475 }
476 @Override public int getSuperclassFieldPadding() {
477 return 4;
478 }
479 };
480 }
481 }
482
483 // In other cases, it's a 64-bit uncompressed OOPs object model
484 return new MemoryLayoutSpecification() {
485 @Override public int getArrayHeaderSize() {
486 return 24;
487 }
488 @Override public int getObjectHeaderSize() {
489 return 16;
490 }
491 @Override public int getObjectPadding() {
492 return 8;
493 }
494 @Override public int getReferenceSize() {
495 return 8;
496 }
497 @Override public int getSuperclassFieldPadding() {
498 return 8;
499 }
500 };
501 }
502 }
--- EOF ---