assylias
assylias

Reputation: 328608

Accurate measurement of object sizes

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 bytes

Object with 1 int: 16 bytes
Object with 2 ints: 24 bytes
Object with 3 ints: 24 bytes

Object with 1 long: 24 bytes
Object with 2 longs: 32 bytes
Object with 3 longs: 40 bytes

Object with 1 reference: 16 bytes
Object with 2 references: 24 bytes
Object with 3 references: 24 bytes

I conclude that:

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);
    }
}

Upvotes: 6

Views: 265

Answers (2)

lichengwu
lichengwu

Reputation: 4307

Java Object's size is known when the class is defined.

Memory usage of Java objects: general http://www.javamex.com/tutorials/memory/object_memory_usage.shtml

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500514

Since references are 8 bytes on a 64-bit JVM

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):

Which oops are compressed?

In an ILP32-mode JVM, or if the UseCompressedOops flag is turned off in LP64 mode, all oops are the native machine word size.

If UseCompressedOops is true, the following oops in the heap will be compressed:

  • the klass field of every object
  • every oop instance field
  • every element of an oop array (objArray)

I suspect this is what's going on in your case.

Test it by using

-XX:-UseCompressedOops

or

-XX:+UseCompressedOops

On my machine, by default I get the same results as you, but with -XX:-UseCompressedOops I see:

Object:16 bytes
Object with 1 int:24 bytes
Object with 2 ints:24 bytes
Object with 3 ints:32 bytes
Object with 1 long:24 bytes
Object with 2 longs:32 bytes
Object with 3 longs:40 bytes
Object with 1 reference:24 bytes
Object with 2 references:32 bytes
Object with 3 references:40 bytes

... which is probably closer to what you were expecting :)

Upvotes: 6

Related Questions