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 ---