| /* |
| * Copyright (c) 2010, 2013, 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; |
| |
| /** |
| * 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. |
| */ |
| public interface MemoryLayoutSpecification { |
| |
| /** |
| * 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 {@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. |
| * |
| * @return the quantum field size for an object. |
| */ |
| int getObjectPadding(); |
| |
| /** |
| * 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. |
| * |
| * @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 {@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 |
| * {@code memoryLayoutSpecification}. |
| * |
| * @param memoryLayoutSpecification a description of the JVM memory layout. |
| */ |
| public ObjectSizeCalculator(final MemoryLayoutSpecification memoryLayoutSpecification) { |
| memoryLayoutSpecification.getClass(); |
| 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. |
| * |
| * @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 histograpm |
| * @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[newReferenceFields.size()]); |
| } |
| |
| 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 |
| * @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).longValue(); |
| } |
| } 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; |
| } |
| }; |
| } |
| } |