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 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 histograpm
 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[newReferenceFields.size()]);
 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).longValue();
 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 }
--- EOF ---