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 }