ObjectSizeCalculator.java revision 1423:c13179703f65
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 26package jdk.nashorn.internal.ir.debug; 27 28import java.lang.reflect.Array; 29import java.lang.reflect.Field; 30import java.lang.reflect.InvocationTargetException; 31import java.lang.reflect.Method; 32import java.lang.reflect.Modifier; 33import java.util.ArrayDeque; 34import java.util.ArrayList; 35import java.util.Arrays; 36import java.util.Deque; 37import java.util.IdentityHashMap; 38import java.util.LinkedList; 39import java.util.List; 40import java.util.Map; 41import 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 */ 56public 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[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); 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} 504