/*
 * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.ir.debug;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

Contains utility methods for calculating the memory usage of objects. It only works on the HotSpot JVM, and infers the actual memory layout (32 bit vs. 64 bit word size, compressed object pointers vs. uncompressed) from best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM. It can only make an educated guess at whether compressed OOPs are used, though; specifically, it knows what the JVM's default choice of OOP compression would be based on HotSpot version and maximum heap sizes, but if the choice is explicitly overridden with the -XX:{+|-}UseCompressedOops command line switch, it can not detect this fact and will report incorrect sizes, as it will presume the default JVM behavior.
/** * Contains utility methods for calculating the memory usage of objects. It * only works on the HotSpot JVM, and infers the actual memory layout (32 bit * vs. 64 bit word size, compressed object pointers vs. uncompressed) from * best available indicators. It can reliably detect a 32 bit vs. 64 bit JVM. * It can only make an educated guess at whether compressed OOPs are used, * though; specifically, it knows what the JVM's default choice of OOP * compression would be based on HotSpot version and maximum heap sizes, but if * the choice is explicitly overridden with the <tt>-XX:{+|-}UseCompressedOops</tt> command line * switch, it can not detect * this fact and will report incorrect sizes, as it will presume the default JVM * behavior. */
public final class ObjectSizeCalculator {
Describes constant memory overheads for various constructs in a JVM implementation.
/** * Describes constant memory overheads for various constructs in a JVM implementation. */
public interface MemoryLayoutSpecification {
Returns the fixed overhead of an array of any type or length in this JVM.
Returns:the fixed overhead of an array.
/** * Returns the fixed overhead of an array of any type or length in this JVM. * * @return the fixed overhead of an array. */
int getArrayHeaderSize();
Returns the fixed overhead of for any Object subclass in this JVM.
Returns:the fixed overhead of any object.
/** * Returns the fixed overhead of for any {@link Object} subclass in this JVM. * * @return the fixed overhead of any object. */
int getObjectHeaderSize();
Returns the quantum field size for a field owned by an object in this JVM.
Returns:the quantum field size for an object.
/** * Returns the quantum field size for a field owned by an object in this JVM. * * @return the quantum field size for an object. */
int getObjectPadding();
Returns the fixed size of an object reference in this JVM.
Returns:the size of all object references.
/** * Returns the fixed size of an object reference in this JVM. * * @return the size of all object references. */
int getReferenceSize();
Returns the quantum field size for a field owned by one of an object's ancestor superclasses in this JVM.
Returns:the quantum field size for a superclass field.
/** * Returns the quantum field size for a field owned by one of an object's ancestor superclasses * in this JVM. * * @return the quantum field size for a superclass field. */
int getSuperclassFieldPadding(); } private static class CurrentLayout { private static final MemoryLayoutSpecification SPEC = getEffectiveMemoryLayoutSpecification(); }
Given an object, returns the total allocated size, in bytes, of the object and all other objects reachable from it. Attempts to to detect the current JVM memory layout, but may fail with UnsupportedOperationException;
Params:
  • obj – the object; can be null. Passing in a Class object doesn't do anything special, it measures the size of all objects reachable through it (which will include its class loader, and by extension, all other Class objects loaded by the same loader, and all the parent class loaders). It doesn't provide the size of the static fields in the JVM class that the Class object represents.
Throws:
Returns:the total allocated size of the object and all other objects it retains.
/** * Given an object, returns the total allocated size, in bytes, of the object * and all other objects reachable from it. Attempts to to detect the current JVM memory layout, * but may fail with {@link UnsupportedOperationException}; * * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do * anything special, it measures the size of all objects * reachable through it (which will include its class loader, and by * extension, all other Class objects loaded by * the same loader, and all the parent class loaders). It doesn't provide the * size of the static fields in the JVM class that the Class object * represents. * @return the total allocated size of the object and all other objects it * retains. * @throws UnsupportedOperationException if the current vm memory layout cannot be detected. */
public static long getObjectSize(final Object obj) throws UnsupportedOperationException { return obj == null ? 0 : new ObjectSizeCalculator(CurrentLayout.SPEC).calculateObjectSize(obj); } // Fixed object header size for arrays. private final int arrayHeaderSize; // Fixed object header size for non-array objects. private final int objectHeaderSize; // Padding for the object size - if the object size is not an exact multiple // of this, it is padded to the next multiple. private final int objectPadding; // Size of reference (pointer) fields. private final int referenceSize; // Padding for the fields of superclass before fields of subclasses are // added. private final int superclassFieldPadding; private final Map<Class<?>, ClassSizeInfo> classSizeInfos = new IdentityHashMap<>(); private final Map<Object, Object> alreadyVisited = new IdentityHashMap<>(); private final Map<Class<?>, ClassHistogramElement> histogram = new IdentityHashMap<>(); private final Deque<Object> pending = new ArrayDeque<>(16 * 1024); private long size;
Creates an object size calculator that can calculate object sizes for a given memoryLayoutSpecification.
Params:
  • memoryLayoutSpecification – a description of the JVM memory layout.
/** * Creates an object size calculator that can calculate object sizes for a given * {@code memoryLayoutSpecification}. * * @param memoryLayoutSpecification a description of the JVM memory layout. */
public ObjectSizeCalculator(final MemoryLayoutSpecification memoryLayoutSpecification) { Objects.requireNonNull(memoryLayoutSpecification); arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize(); objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize(); objectPadding = memoryLayoutSpecification.getObjectPadding(); referenceSize = memoryLayoutSpecification.getReferenceSize(); superclassFieldPadding = memoryLayoutSpecification.getSuperclassFieldPadding(); }
Given an object, returns the total allocated size, in bytes, of the object and all other objects reachable from it.
Params:
  • obj – the object; can be null. Passing in a Class object doesn't do anything special, it measures the size of all objects reachable through it (which will include its class loader, and by extension, all other Class objects loaded by the same loader, and all the parent class loaders). It doesn't provide the size of the static fields in the JVM class that the Class object represents.
Returns:the total allocated size of the object and all other objects it retains.
/** * Given an object, returns the total allocated size, in bytes, of the object * and all other objects reachable from it. * * @param obj the object; can be null. Passing in a {@link java.lang.Class} object doesn't do * anything special, it measures the size of all objects * reachable through it (which will include its class loader, and by * extension, all other Class objects loaded by * the same loader, and all the parent class loaders). It doesn't provide the * size of the static fields in the JVM class that the Class object * represents. * @return the total allocated size of the object and all other objects it * retains. */
public synchronized long calculateObjectSize(final Object obj) { // Breadth-first traversal instead of naive depth-first with recursive // implementation, so we don't blow the stack traversing long linked lists. histogram.clear(); try { for (Object o = obj;;) { visit(o); if (pending.isEmpty()) { return size; } o = pending.removeFirst(); } } finally { alreadyVisited.clear(); pending.clear(); size = 0; } }
Get the class histogram
Returns:class histogram element list
/** * Get the class histogram * @return class histogram element list */
public List<ClassHistogramElement> getClassHistogram() { return new ArrayList<>(histogram.values()); } private ClassSizeInfo getClassSizeInfo(final Class<?> clazz) { ClassSizeInfo csi = classSizeInfos.get(clazz); if(csi == null) { csi = new ClassSizeInfo(clazz); classSizeInfos.put(clazz, csi); } return csi; } private void visit(final Object obj) { if (alreadyVisited.containsKey(obj)) { return; } final Class<?> clazz = obj.getClass(); if (clazz == ArrayElementsVisitor.class) { ((ArrayElementsVisitor) obj).visit(this); } else { alreadyVisited.put(obj, obj); if (clazz.isArray()) { visitArray(obj); } else { getClassSizeInfo(clazz).visit(obj, this); } } } private void visitArray(final Object array) { final Class<?> arrayClass = array.getClass(); final Class<?> componentType = arrayClass.getComponentType(); final int length = Array.getLength(array); if (componentType.isPrimitive()) { increaseByArraySize(arrayClass, length, getPrimitiveFieldSize(componentType)); } else { increaseByArraySize(arrayClass, length, referenceSize); // If we didn't use an ArrayElementsVisitor, we would be enqueueing every // element of the array here instead. For large arrays, it would // tremendously enlarge the queue. In essence, we're compressing it into // a small command object instead. This is different than immediately // visiting the elements, as their visiting is scheduled for the end of // the current queue. switch (length) { case 0: { break; } case 1: { enqueue(Array.get(array, 0)); break; } default: { enqueue(new ArrayElementsVisitor((Object[]) array)); } } } } private void increaseByArraySize(final Class<?> clazz, final int length, final long elementSize) { increaseSize(clazz, roundTo(arrayHeaderSize + length * elementSize, objectPadding)); } private static class ArrayElementsVisitor { private final Object[] array; ArrayElementsVisitor(final Object[] array) { this.array = array; } public void visit(final ObjectSizeCalculator calc) { for (final Object elem : array) { if (elem != null) { calc.visit(elem); } } } } void enqueue(final Object obj) { if (obj != null) { pending.addLast(obj); } } void increaseSize(final Class<?> clazz, final long objectSize) { ClassHistogramElement he = histogram.get(clazz); if(he == null) { he = new ClassHistogramElement(clazz); histogram.put(clazz, he); } he.addInstance(objectSize); size += objectSize; } static long roundTo(final long x, final int multiple) { return ((x + multiple - 1) / multiple) * multiple; } private class ClassSizeInfo { // Padded fields + header size private final long objectSize; // Only the fields size - used to calculate the subclasses' memory // footprint. private final long fieldsSize; private final Field[] referenceFields; public ClassSizeInfo(final Class<?> clazz) { long newFieldsSize = 0; final List<Field> newReferenceFields = new LinkedList<>(); for (final Field f : clazz.getDeclaredFields()) { if (Modifier.isStatic(f.getModifiers())) { continue; } final Class<?> type = f.getType(); if (type.isPrimitive()) { newFieldsSize += getPrimitiveFieldSize(type); } else { f.setAccessible(true); newReferenceFields.add(f); newFieldsSize += referenceSize; } } final Class<?> superClass = clazz.getSuperclass(); if (superClass != null) { final ClassSizeInfo superClassInfo = getClassSizeInfo(superClass); newFieldsSize += roundTo(superClassInfo.fieldsSize, superclassFieldPadding); newReferenceFields.addAll(Arrays.asList(superClassInfo.referenceFields)); } this.fieldsSize = newFieldsSize; this.objectSize = roundTo(objectHeaderSize + newFieldsSize, objectPadding); this.referenceFields = newReferenceFields.toArray( new Field[0]); } void visit(final Object obj, final ObjectSizeCalculator calc) { calc.increaseSize(obj.getClass(), objectSize); enqueueReferencedObjects(obj, calc); } public void enqueueReferencedObjects(final Object obj, final ObjectSizeCalculator calc) { for (final Field f : referenceFields) { try { calc.enqueue(f.get(obj)); } catch (final IllegalAccessException e) { final AssertionError ae = new AssertionError( "Unexpected denial of access to " + f); ae.initCause(e); throw ae; } } } } private static long getPrimitiveFieldSize(final Class<?> type) { if (type == boolean.class || type == byte.class) { return 1; } if (type == char.class || type == short.class) { return 2; } if (type == int.class || type == float.class) { return 4; } if (type == long.class || type == double.class) { return 8; } throw new AssertionError("Encountered unexpected primitive type " + type.getName()); } // ALERT: java.lang.management is not available in compact 1. We need // to use reflection to soft link test memory statistics. static Class<?> managementFactory = null; static Class<?> memoryPoolMXBean = null; static Class<?> memoryUsage = null; static Method getMemoryPoolMXBeans = null; static Method getUsage = null; static Method getMax = null; static { try { managementFactory = Class.forName("java.lang.management.ManagementFactory"); memoryPoolMXBean = Class.forName("java.lang.management.MemoryPoolMXBean"); memoryUsage = Class.forName("java.lang.management.MemoryUsage"); getMemoryPoolMXBeans = managementFactory.getMethod("getMemoryPoolMXBeans"); getUsage = memoryPoolMXBean.getMethod("getUsage"); getMax = memoryUsage.getMethod("getMax"); } catch (ClassNotFoundException | NoSuchMethodException | SecurityException ex) { // Pass thru, asserts when attempting to use. } }
Return the current memory usage
Returns:current memory usage derived from system configuration
/** * Return the current memory usage * @return current memory usage derived from system configuration */
public static MemoryLayoutSpecification getEffectiveMemoryLayoutSpecification() { final String vmName = System.getProperty("java.vm.name"); if (vmName == null || !vmName.startsWith("Java HotSpot(TM) ")) { throw new UnsupportedOperationException( "ObjectSizeCalculator only supported on HotSpot VM"); } final String dataModel = System.getProperty("sun.arch.data.model"); if ("32".equals(dataModel)) { // Running with 32-bit data model return new MemoryLayoutSpecification() { @Override public int getArrayHeaderSize() { return 12; } @Override public int getObjectHeaderSize() { return 8; } @Override public int getObjectPadding() { return 8; } @Override public int getReferenceSize() { return 4; } @Override public int getSuperclassFieldPadding() { return 4; } }; } else if (!"64".equals(dataModel)) { throw new UnsupportedOperationException("Unrecognized value '" + dataModel + "' of sun.arch.data.model system property"); } final String strVmVersion = System.getProperty("java.vm.version"); final int vmVersion = Integer.parseInt(strVmVersion.substring(0, strVmVersion.indexOf('.'))); if (vmVersion >= 17) { long maxMemory = 0; /* See ALERT above. The reflection code below duplicates the following sequence, and avoids hard coding of java.lang.management. for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) { maxMemory += mp.getUsage().getMax(); } */ if (getMemoryPoolMXBeans == null) { throw new AssertionError("java.lang.management not available in compact 1"); } try { final List<?> memoryPoolMXBeans = (List<?>)getMemoryPoolMXBeans.invoke(managementFactory); for (final Object mp : memoryPoolMXBeans) { final Object usage = getUsage.invoke(mp); final Object max = getMax.invoke(usage); maxMemory += ((Long)max); } } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new AssertionError("java.lang.management not available in compact 1"); } if (maxMemory < 30L * 1024 * 1024 * 1024) { // HotSpot 17.0 and above use compressed OOPs below 30GB of RAM total // for all memory pools (yes, including code cache). return new MemoryLayoutSpecification() { @Override public int getArrayHeaderSize() { return 16; } @Override public int getObjectHeaderSize() { return 12; } @Override public int getObjectPadding() { return 8; } @Override public int getReferenceSize() { return 4; } @Override public int getSuperclassFieldPadding() { return 4; } }; } } // In other cases, it's a 64-bit uncompressed OOPs object model return new MemoryLayoutSpecification() { @Override public int getArrayHeaderSize() { return 24; } @Override public int getObjectHeaderSize() { return 16; } @Override public int getObjectPadding() { return 8; } @Override public int getReferenceSize() { return 8; } @Override public int getSuperclassFieldPadding() { return 8; } }; } }