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