Monitoring the size of your Java objects with Java Object Layout

In this tutorial you will learn how you can use the JOL (Java Object Layout) toolbox to monitor the size of Java Objects.

JOL is tiny toolbox available in OpenJDK to analyze Java object layout schemes. This tool uses Unsafe, JVMTI, and Serviceability Agent (SA) heavily to decoder the actual object layout, footprint, and references. This makes JOL much more accurate than other tools relying on heap dumps, specification assumptions, etc.

You can use JOL in different ways, as a Command Line Tool or using directly its API in your code.

Using JOL as Command Line Utility

As command line utility, you will have at first to clone the project available at: http://hg.openjdk.java.net/code-tools/jol/ Then build JOL with Maven:

$ cd jol/
$ mvn clean install

Now you can run the executable JAR file jol-cli.jar to get:

  • Object internals, that is instances of that object
  • Object externals, that is, the objects reachable from the instance
$ java -jar jol-cli/target/jol-cli.jar internals java.util.HashMap
Running 64-bit HotSpot VM.
Using compressed oop with 3-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

java.util.HashMap object internals:
OFFSET  SIZE       TYPE DESCRIPTION                    VALUE
  0     4            (object header)                05 00 00 00 (0000 0101 0000 0000 0000 0000 0000 0000)
  4     4            (object header)                00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
  8     4            (object header)                8c 3b 00 f8 (1000 1100 0011 1011 0000 0000 1111 1000)
 12     4        Set AbstractMap.keySet             null
 16     4 Collection AbstractMap.values             null
 20     4        int HashMap.size                   0
 24     4        int HashMap.modCount               0
 28     4        int HashMap.threshold              0
 32     4      float HashMap.loadFactor             0.75
 36     4     Node[] HashMap.table                  null
 40     4        Set HashMap.entrySet               null
 44     4            (loss due to the next object alignment)
Instance size: 48 bytes (reported by Instrumentation API)
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Calculate objects size from your code

if you need to calculate at runtime the size of your objects, then you need at first to include its dependency so that you can build your project:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.8</version>
</dependency>

Once done, you will be able to access the org.openjdk.jol package as in the following example class:

package com.mastertheboss;

import java.util.ArrayList;
import java.util.List;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.info.GraphLayout;
 
public class MemoryTest {
	@SuppressWarnings("restriction")
	public static void main(String[] args) {
        int size = 10;
        List<Integer> list = new ArrayList<Integer>(size);
        for (int i = 0; i < size; i++) {
            list.add(i);
        }
     
        System.out.println(ClassLayout.parseClass(ArrayList.class).toPrintable()); 
        System.out.println(ClassLayout.parseClass(ArrayList.class).toPrintable(list));        
        System.out.println(GraphLayout.parseInstance(list).toPrintable());
        System.out.println(GraphLayout.parseInstance(list).toFootprint());
    }
}

The first method, that is:

System.out.println(ClassLayout.parseClass(ArrayList.class).toPrintable()); 

Produces a printable stringly representation of class layout. This method does not require alive instance, just the class.

java.util.ArrayList object internals:
 OFFSET  SIZE                 TYPE DESCRIPTION                               VALUE
      0    12                      (object header)                           N/A
     12     4                  int AbstractList.modCount                     N/A
     16     4                  int ArrayList.size                            N/A
     20     4   java.lang.Object[] ArrayList.elementData                     N/A
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

On the other hand, next one:

System.out.println(ClassLayout.parseClass(ArrayList.class).toPrintable(list));        

Produces a String representation of a live class instance with its objects:

java.util.ArrayList object internals:
 OFFSET  SIZE                 TYPE DESCRIPTION                               VALUE
      0     4                      (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4                      (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4                      (object header)                           9e 36 00 f8 (10011110 00110110 00000000 11111000) (-134203746)
     12     4                  int AbstractList.modCount                     10
     16     4                  int ArrayList.size                            10
     20     4   java.lang.Object[] ArrayList.elementData                     [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total

In order to produce a representation of graph of external objects, we’ll use the following method:

System.out.println(GraphLayout.parseInstance(list).toPrintable());

Which produces this output for our ArrayList:

java.util.ArrayList@368102c8d object externals:
          ADDRESS       SIZE TYPE                PATH                           VALUE
        782faf2d0         16 java.lang.Integer   .elementData[0]                0
        782faf2e0         16 java.lang.Integer   .elementData[1]                1
        782faf2f0         16 java.lang.Integer   .elementData[2]                2
        782faf300         16 java.lang.Integer   .elementData[3]                3
        782faf310         16 java.lang.Integer   .elementData[4]                4
        782faf320         16 java.lang.Integer   .elementData[5]                5
        782faf330         16 java.lang.Integer   .elementData[6]                6
        782faf340         16 java.lang.Integer   .elementData[7]                7
        782faf350         16 java.lang.Integer   .elementData[8]                8
        782faf360         16 java.lang.Integer   .elementData[9]                9
        782faf370     246184 (something else)    (somewhere else)               (something else)
        782feb518         24 java.util.ArrayList                                (object)
        782feb530         56 [Ljava.lang.Object; .elementData                   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

And finally, in order to calculate the memory footprint of the ArrayList:

System.out.println(GraphLayout.parseInstance(list).toFootprint());

Here’s the output:

java.util.ArrayList@368102c8d footprint:
     COUNT       AVG       SUM   DESCRIPTION
         1        56        56   [Ljava.lang.Object;
        10        16       160   java.lang.Integer
         1        24        24   java.util.ArrayList
        12                 240   (total)