Skip to content

Commit 697f8df

Browse files
committed
More cleanups, experiments and tests.
1 parent 3b8e4d0 commit 697f8df

File tree

7 files changed

+220
-132
lines changed

7 files changed

+220
-132
lines changed

pom.xml

+7
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@
5050
<type>jar</type>
5151
<scope>test</scope>
5252
</dependency>
53+
54+
<dependency>
55+
<groupId>com.carrotsearch</groupId>
56+
<artifactId>junit-benchmarks</artifactId>
57+
<version>0.3.0</version>
58+
<scope>test</scope>
59+
</dependency>
5360
</dependencies>
5461

5562

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.carrotsearch.sizeof;
2+
3+
import java.lang.reflect.Field;
4+
import java.nio.ByteOrder;
5+
import java.util.Locale;
6+
import java.util.Map;
7+
import java.util.TreeMap;
8+
9+
import sun.misc.Unsafe;
10+
11+
/**
12+
* This class contains black magic stuff based mostly on proprietary
13+
* APIs and hacks. Use at your own risk.
14+
*/
15+
@SuppressWarnings({"restriction"})
16+
public final class BlackMagic {
17+
/**
18+
* Returns Unsafe if available or throw a RuntimeException.
19+
*/
20+
public static sun.misc.Unsafe getUnsafe() {
21+
try {
22+
final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
23+
final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
24+
unsafeField.setAccessible(true);
25+
return (sun.misc.Unsafe) unsafeField.get(null);
26+
} catch (Throwable t) {
27+
throw new RuntimeException("Unsafe not available.", t);
28+
}
29+
}
30+
31+
/**
32+
* Attempts to dump physical object's memory as a string.
33+
*/
34+
public static String objectMemoryAsString(Object o) {
35+
final Unsafe unsafe = getUnsafe();
36+
final ByteOrder byteOrder = ByteOrder.nativeOrder();
37+
38+
StringBuilder b = new StringBuilder();
39+
final int obSize = (int) RamUsageEstimator.shallowSizeOf(o);
40+
for (int i = 0; i < obSize; i += 2) {
41+
if ((i & 0xf) == 0) {
42+
if (i > 0) b.append("\n");
43+
b.append(String.format(Locale.ENGLISH, "%#06x", i));
44+
}
45+
46+
// we go short by short because J9 fails on odd addresses.
47+
int shortValue = unsafe.getShort(o, (long) i);
48+
49+
if (byteOrder == ByteOrder.BIG_ENDIAN) {
50+
b.append(String.format(Locale.ENGLISH, " %02x", (shortValue >>> 8) & 0xff));
51+
b.append(String.format(Locale.ENGLISH, " %02x", (shortValue & 0xff)));
52+
} else {
53+
b.append(String.format(Locale.ENGLISH, " %02x", (shortValue & 0xff)));
54+
b.append(String.format(Locale.ENGLISH, " %02x", (shortValue >>> 8) & 0xff));
55+
}
56+
}
57+
return b.toString();
58+
}
59+
60+
/**
61+
* Attempts to dump a layout of a class's fields in memory
62+
* (offsets from base object pointer).
63+
*/
64+
@SuppressWarnings({"unchecked"})
65+
public static String fieldsLayoutAsString(Class<?> clazz) {
66+
Unsafe unsafe = getUnsafe();
67+
TreeMap<Long, String> fields = new TreeMap<Long, String>();
68+
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
69+
for (Field f : c.getDeclaredFields()) {
70+
fields.put(
71+
unsafe.objectFieldOffset(f),
72+
f.getDeclaringClass().getSimpleName() + "." + f.getName());
73+
}
74+
}
75+
fields.put(
76+
RamUsageEstimator.shallowSizeOfInstance(clazz), "#shallowSizeOfInstance(" + clazz.getName() + ")");
77+
78+
StringBuilder b = new StringBuilder();
79+
Object [] entries = fields.entrySet().toArray();
80+
for (int i = 0; i < entries.length; i++) {
81+
Map.Entry<Long, String> e = (Map.Entry<Long, String>) entries[i];
82+
Map.Entry<Long, String> next = (i + 1 < entries.length ? (Map.Entry<Long, String>) entries[i + 1] : null);
83+
84+
b.append(String.format(Locale.ENGLISH,
85+
"@%02d %2s %s\n",
86+
e.getKey(),
87+
next == null ? "" : next.getKey() - e.getKey(),
88+
e.getValue()));
89+
}
90+
return b.toString();
91+
}
92+
}

src/main/java/com/carrotsearch/sizeof/RamUsageEstimator.java

+62-31
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
* @see #sizeOf(Object)
3434
*/
3535
public final class RamUsageEstimator {
36-
3736
/**
3837
* JVM diagnostic features.
3938
*/
@@ -232,6 +231,19 @@ private RamUsageEstimator() {}
232231
Constants.JAVA_VENDOR + ", " + Constants.JAVA_VERSION + "]";
233232
}
234233

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+
235247
// Object with just one field to determine the object header size by getting the offset of the dummy field:
236248
@SuppressWarnings("unused")
237249
private static final class DummyOneFieldObject {
@@ -315,8 +327,9 @@ public static long sizeOf(double[] arr) {
315327
*/
316328
public static long sizeOf(Object obj) {
317329
final Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<Object,Boolean>(64));
330+
final Map<Class<?>, ClassCache> classCache = new HashMap<Class<?>, ClassCache>();
318331
try {
319-
return measureObjectSize(obj, seen);
332+
return measureObjectSize(obj, seen, classCache);
320333
} finally {
321334
// Help the GC.
322335
seen.clear();
@@ -334,7 +347,7 @@ public static long shallowSizeOf(Object obj) {
334347
if (obj == null) return 0;
335348
final Class<?> clz = obj.getClass();
336349
if (clz.isArray()) {
337-
return measureArraySize(obj, null);
350+
return measureArraySize(obj, null, null);
338351
} else {
339352
return shallowSizeOfInstance(clz);
340353
}
@@ -371,48 +384,66 @@ public static long shallowSizeOfInstance(Class<?> clazz) {
371384
/**
372385
* Recursive descend into an object.
373386
*/
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)) {
376389
return 0;
377390
}
378391

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.
385393
seen.add(obj);
386394

387-
Class<?> clazz = obj.getClass();
395+
final Class<?> clazz = obj.getClass();
388396
if (clazz.isArray()) {
389-
return measureArraySize(obj, seen);
397+
return measureArraySize(obj, seen, classCache);
390398
}
391399

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+
}
394420

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();
398431
for (final Field f : fields) {
399432
if (!Modifier.isStatic(f.getModifiers())) {
400-
objShallowSize = adjustForField(objShallowSize, f);
433+
shallowInstanceSize = adjustForField(shallowInstanceSize, f);
401434

402435
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);
411438
}
412439
}
413440
}
414441
}
415-
return alignObjectSize(objShallowSize) + referencedSize;
442+
443+
cachedInfo = new ClassCache(
444+
shallowInstanceSize,
445+
referenceFields.toArray(new Field[referenceFields.size()]));
446+
return cachedInfo;
416447
}
417448

418449
/**
@@ -445,7 +476,7 @@ private static long adjustForField(long sizeSoFar, final Field f) {
445476
f.getDeclaringClass().getName() + "#" + f.getName(), cause);
446477
}
447478
} else {
448-
// TODO: No alignments/ subclass field alignments whatsoever?
479+
// TODO: No alignments based on field type/ subclass fields alignments?
449480
return sizeSoFar + fsize;
450481
}
451482
}
@@ -456,7 +487,7 @@ private static long adjustForField(long sizeSoFar, final Field f) {
456487
* @param seen A set of already seen objects. If <code>null</code> no references
457488
* are followed and this method returns the equivalent of "shallow" array size.
458489
*/
459-
private static long measureArraySize(Object array, Set<Object> seen) {
490+
private static long measureArraySize(Object array, Set<Object> seen, Map<Class<?>, ClassCache> classCache) {
460491
long size = NUM_BYTES_ARRAY_HEADER;
461492
final int len = Array.getLength(array);
462493
if (len > 0) {
@@ -467,7 +498,7 @@ private static long measureArraySize(Object array, Set<Object> seen) {
467498
size += (long) NUM_BYTES_OBJECT_REF * len;
468499
if (seen != null) {
469500
for (int i = 0; i < len; i++) {
470-
size += measureObjectSize(Array.get(array, i), seen);
501+
size += measureObjectSize(Array.get(array, i), seen, classCache);
471502
}
472503
}
473504
}

src/test/java/com/carrotsearch/sizeof/experiments/ExpFieldAlignment.java renamed to src/test/java/com/carrotsearch/sizeof/TestFieldReorderingInClassHierarchy.java

+10-17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.carrotsearch.sizeof.experiments;
1+
package com.carrotsearch.sizeof;
22

33
import java.lang.reflect.Field;
44
import java.util.Map;
@@ -8,16 +8,17 @@
88

99
import sun.misc.Unsafe;
1010

11+
import com.carrotsearch.sizeof.BlackMagic;
1112
import com.carrotsearch.sizeof.RamUsageEstimator;
13+
import com.carrotsearch.sizeof.experiments.WildClasses;
1214

1315
@SuppressWarnings("restriction")
14-
public class ExpFieldAlignment {
16+
public class TestFieldReorderingInClassHierarchy {
1517

1618
@SuppressWarnings({"deprecation"})
1719
@Test
1820
public void testFieldsOrdered() throws Exception {
19-
System.out.println(RamUsageEstimator.JVM_INFO_STRING);
20-
Unsafe unsafe = getUnsafe();
21+
Unsafe unsafe = BlackMagic.getUnsafe();
2122
for (Class<?> clz : WildClasses.ALL) {
2223
TreeMap<Integer, Field> fields = new TreeMap<Integer, Field>();
2324
for (Class<?> c = clz; c != null; c = c.getSuperclass()) {
@@ -42,20 +43,12 @@ public void testFieldsOrdered() throws Exception {
4243
}
4344

4445
if (reordered > 0) {
45-
System.out.println("# packed fields: " + reordered);
46-
System.out.println(ExpSubclassAlignment.dumpFields(clz));
46+
System.out.println("This JVM has reordered fields!");
47+
System.out.println(RamUsageEstimator.JVM_INFO_STRING);
48+
System.out.println("Example class with reordered fields:");
49+
System.out.println(BlackMagic.fieldsLayoutAsString(clz));
50+
return;
4751
}
4852
}
4953
}
50-
51-
public static sun.misc.Unsafe getUnsafe() {
52-
try {
53-
final Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
54-
final Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
55-
unsafeField.setAccessible(true);
56-
return (sun.misc.Unsafe) unsafeField.get(null);
57-
} catch (Throwable t) {
58-
throw new RuntimeException("Unsafe not available.", t);
59-
}
60-
}
6154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.carrotsearch.sizeof;
2+
3+
import java.util.HashSet;
4+
import java.util.Random;
5+
6+
import org.junit.BeforeClass;
7+
import org.junit.Test;
8+
9+
import com.carrotsearch.junitbenchmarks.AbstractBenchmark;
10+
import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
11+
import com.carrotsearch.sizeof.experiments.WildClasses;
12+
13+
/**
14+
* Check large graph of instances with lots of classes.
15+
*/
16+
public class TestProcessingLargeGraph extends AbstractBenchmark {
17+
private static HashSet<Object> randomObjects;
18+
19+
@BeforeClass
20+
public static void prepare() throws Exception {
21+
final Random rnd = new Random(0xdeadbeef);
22+
HashSet<Object> all = new HashSet<Object>();
23+
Class<?>[] classes = WildClasses.ALL;
24+
for (int i = 0; i < 50000; i++) {
25+
all.add(classes[rnd.nextInt(classes.length)].newInstance());
26+
}
27+
randomObjects = all;
28+
}
29+
30+
volatile long guard;
31+
32+
@BenchmarkOptions(callgc = false, benchmarkRounds = 5, warmupRounds = 3)
33+
@Test
34+
public void testWildClasses() {
35+
guard = RamUsageEstimator.sizeOf(randomObjects);
36+
System.out.println(guard);
37+
}
38+
}

0 commit comments

Comments
 (0)