The code at the bottom of this question is a bit long but basically creates a few objects and determines their size in memory. I execute the code with the following JVM parameters (TLAB to avoid chunk memory allocation and supposedly get accurate memory usage figures):
-server -Xms2000m -Xmx2000m -verbose:gc -XX:-UseTLAB
I run the code on a 64 bit Hotspot JVM and get the following output:
Java HotSpot(TM) 64-Bit Server VM
Object: 16 bytesObject with 1 int: 16 bytes
Object with 2 ints: 24 bytes
Object with 3 ints: 24 bytesObject with 1 long: 24 bytes
Object with 2 longs: 32 bytes
Object with 3 longs: 40 bytesObject with 1 reference: 16 bytes
Object with 2 references: 24 bytes
Object with 3 references: 24 bytes
I conclude that:
- an object takes 12 bytes, aligned to 16 bytes.
- an int takes 4 bytes (1 object with one int is 12 + 4 = still 16 bytes, with 2 ints: 12 + 8 = 20 aligned to 24 bytes)
- a long takes 8 bytes (1 object with one long is 12 + 8 = 20 bytes, aligned to 24 bytes)
But I struggle to understand why references don’t use as much space as longs.
Since references are 8 bytes on a 64-bit JVM, the obvious conclusion is that the measurement methodology has a flaw*.
Can you explain what is going on and what can be done to fix it?
*Notes:
– no GC runs during the measurement.
– using the Netbeans profiler yields similar results.
public class TestMemoryReference {
private static final int SIZE = 100_000;
private static Runnable r;
private static Object o = new Object();
private static Object o1 = new Object();
private static Object o2 = new Object();
private static Object o3 = new Object();
public static class ObjectWith1Int { int i; }
public static class ObjectWith2Ints { int i, j; }
public static class ObjectWith3Ints { int i, j, k; }
public static class ObjectWith1Long { long i; }
public static class ObjectWith2Longs { long i, j; }
public static class ObjectWith3Longs { long i, j, k; }
public static class ObjectWith1Object { Object o = o1; }
public static class ObjectWith2Objects { Object o = o1; Object p = o2; }
public static class ObjectWith3Objects { Object o = o1; Object p = o2; Object q = o3; }
private static void test(Runnable r, String name, int numberOfObjects) {
long mem = Runtime.getRuntime().freeMemory();
r.run();
System.out.println(name + ":" + (mem - Runtime.getRuntime().freeMemory()) / numberOfObjects + " bytes ");
}
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.vm.name") + " ");
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new Object(); } };
test(r, "Object", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Int(); } };
test(r, "Object with 1 int", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Ints(); } };
test(r, "Object with 2 ints", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Ints(); } };
test(r, "Object with 3 ints", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Long(); } };
test(r, "Object with 1 long", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Longs(); } };
test(r, "Object with 2 longs", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Longs(); } };
test(r, "Object with 3 longs", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith1Object(); } };
test(r, "Object with 1 reference", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith2Objects(); } };
test(r, "Object with 2 references", SIZE);
r = new Runnable() { public void run() { for (int i = 0; i < SIZE; i++) o = new ObjectWith3Objects(); } };
test(r, "Object with 3 references", SIZE);
}
}
This is your potentially-flawed assumption.
HotSpot is able to use "compressed oops" to use 32-bit values for references in some places of the JVM (emphasis mine):
I suspect this is what’s going on in your case.
Test it by using
or
On my machine, by default I get the same results as you, but with
-XX:-UseCompressedOopsI see:… which is probably closer to what you were expecting 🙂