33
33
* @see #sizeOf(Object)
34
34
*/
35
35
public final class RamUsageEstimator {
36
-
37
36
/**
38
37
* JVM diagnostic features.
39
38
*/
@@ -232,6 +231,19 @@ private RamUsageEstimator() {}
232
231
Constants .JAVA_VENDOR + ", " + Constants .JAVA_VERSION + "]" ;
233
232
}
234
233
234
+ /**
235
+ * Cached information about a given class.
236
+ */
237
+ private static final class ClassCache {
238
+ public final long shallowInstanceSize ;
239
+ public final Field [] referenceFields ;
240
+
241
+ public ClassCache (long shallowInstanceSize , Field [] referenceFields ) {
242
+ this .shallowInstanceSize = shallowInstanceSize ;
243
+ this .referenceFields = referenceFields ;
244
+ }
245
+ }
246
+
235
247
// Object with just one field to determine the object header size by getting the offset of the dummy field:
236
248
@ SuppressWarnings ("unused" )
237
249
private static final class DummyOneFieldObject {
@@ -315,8 +327,9 @@ public static long sizeOf(double[] arr) {
315
327
*/
316
328
public static long sizeOf (Object obj ) {
317
329
final Set <Object > seen = Collections .newSetFromMap (new IdentityHashMap <Object ,Boolean >(64 ));
330
+ final Map <Class <?>, ClassCache > classCache = new HashMap <Class <?>, ClassCache >();
318
331
try {
319
- return measureObjectSize (obj , seen );
332
+ return measureObjectSize (obj , seen , classCache );
320
333
} finally {
321
334
// Help the GC.
322
335
seen .clear ();
@@ -334,7 +347,7 @@ public static long shallowSizeOf(Object obj) {
334
347
if (obj == null ) return 0 ;
335
348
final Class <?> clz = obj .getClass ();
336
349
if (clz .isArray ()) {
337
- return measureArraySize (obj , null );
350
+ return measureArraySize (obj , null , null );
338
351
} else {
339
352
return shallowSizeOfInstance (clz );
340
353
}
@@ -371,48 +384,66 @@ public static long shallowSizeOfInstance(Class<?> clazz) {
371
384
/**
372
385
* Recursive descend into an object.
373
386
*/
374
- private static long measureObjectSize (Object obj , Set <Object > seen ) {
375
- if (obj == null ) {
387
+ private static long measureObjectSize (Object obj , Set <Object > seen , Map < Class <?>, ClassCache > classCache ) {
388
+ if (obj == null || seen . contains ( obj ) ) {
376
389
return 0 ;
377
390
}
378
391
379
- // skip if we have seen before
380
- if (seen .contains (obj )) {
381
- return 0 ;
382
- }
383
-
384
- // add to seen
392
+ // Mark as seen.
385
393
seen .add (obj );
386
394
387
- Class <?> clazz = obj .getClass ();
395
+ final Class <?> clazz = obj .getClass ();
388
396
if (clazz .isArray ()) {
389
- return measureArraySize (obj , seen );
397
+ return measureArraySize (obj , seen , classCache );
390
398
}
391
399
392
- long objShallowSize = NUM_BYTES_OBJECT_HEADER ;
393
- long referencedSize = 0L ;
400
+ // Check the cache first, otherwise walk and populate.
401
+ try {
402
+ ClassCache cachedInfo = classCache .get (clazz );
403
+ if (cachedInfo == null ) {
404
+ classCache .put (clazz , cachedInfo = createCacheEntry (clazz ));
405
+ }
406
+
407
+ long referencedSize = 0L ;
408
+ for (Field f : cachedInfo .referenceFields ) {
409
+ final Object o = f .get (obj );
410
+ if (o != null ) {
411
+ referencedSize += measureObjectSize (o , seen , classCache );
412
+ }
413
+ }
414
+ return alignObjectSize (cachedInfo .shallowInstanceSize ) + referencedSize ;
415
+ } catch (IllegalAccessException e ) {
416
+ // this should never happen as we enabled setAccessible().
417
+ throw new RuntimeException ("Reflective field access failed?" , e );
418
+ }
419
+ }
394
420
395
- // walk type hierarchy
396
- for (;clazz != null ; clazz = clazz .getSuperclass ()) {
397
- final Field [] fields = clazz .getDeclaredFields ();
421
+ /**
422
+ * Create a cached information about shallow size and reference fields for
423
+ * a given class.
424
+ */
425
+ private static ClassCache createCacheEntry (final Class <?> clazz ) {
426
+ ClassCache cachedInfo ;
427
+ long shallowInstanceSize = NUM_BYTES_OBJECT_HEADER ;
428
+ final ArrayList <Field > referenceFields = new ArrayList <Field >(32 );
429
+ for (Class <?> c = clazz ; c != null ; c = c .getSuperclass ()) {
430
+ final Field [] fields = c .getDeclaredFields ();
398
431
for (final Field f : fields ) {
399
432
if (!Modifier .isStatic (f .getModifiers ())) {
400
- objShallowSize = adjustForField (objShallowSize , f );
433
+ shallowInstanceSize = adjustForField (shallowInstanceSize , f );
401
434
402
435
if (!f .getType ().isPrimitive ()) {
403
- try {
404
- f .setAccessible (true );
405
- referencedSize += measureObjectSize (f .get (obj ), seen );
406
- } catch (IllegalAccessException ex ) {
407
- // this should never happen as we enabled setAccessible().
408
- throw new RuntimeException ("Cannot reflect instance field: " +
409
- f .getDeclaringClass ().getName () + "#" + f .getName (), ex );
410
- }
436
+ f .setAccessible (true );
437
+ referenceFields .add (f );
411
438
}
412
439
}
413
440
}
414
441
}
415
- return alignObjectSize (objShallowSize ) + referencedSize ;
442
+
443
+ cachedInfo = new ClassCache (
444
+ shallowInstanceSize ,
445
+ referenceFields .toArray (new Field [referenceFields .size ()]));
446
+ return cachedInfo ;
416
447
}
417
448
418
449
/**
@@ -445,7 +476,7 @@ private static long adjustForField(long sizeSoFar, final Field f) {
445
476
f .getDeclaringClass ().getName () + "#" + f .getName (), cause );
446
477
}
447
478
} else {
448
- // TODO: No alignments/ subclass field alignments whatsoever?
479
+ // TODO: No alignments based on field type / subclass fields alignments?
449
480
return sizeSoFar + fsize ;
450
481
}
451
482
}
@@ -456,7 +487,7 @@ private static long adjustForField(long sizeSoFar, final Field f) {
456
487
* @param seen A set of already seen objects. If <code>null</code> no references
457
488
* are followed and this method returns the equivalent of "shallow" array size.
458
489
*/
459
- private static long measureArraySize (Object array , Set <Object > seen ) {
490
+ private static long measureArraySize (Object array , Set <Object > seen , Map < Class <?>, ClassCache > classCache ) {
460
491
long size = NUM_BYTES_ARRAY_HEADER ;
461
492
final int len = Array .getLength (array );
462
493
if (len > 0 ) {
@@ -467,7 +498,7 @@ private static long measureArraySize(Object array, Set<Object> seen) {
467
498
size += (long ) NUM_BYTES_OBJECT_REF * len ;
468
499
if (seen != null ) {
469
500
for (int i = 0 ; i < len ; i ++) {
470
- size += measureObjectSize (Array .get (array , i ), seen );
501
+ size += measureObjectSize (Array .get (array , i ), seen , classCache );
471
502
}
472
503
}
473
504
}
0 commit comments