Sizes of different types of Java objects

Introduction



Does the Java object contain:





Answers to these (and other) questions can be obtained using the org.openjdk.jol class library, which, in particular, allows us to understand that the object is a memory area:





Training



Here are the results of evaluating the memory of objects of various types by the method from the description of the java.lang.instrument package (see also here ). These results allow us to answer most of the questions posed above.



The following steps must be completed:



  1. Create an agent class containing the premain method:

    public static void premain(String, Instrumentation) {...}
          
          



  2. Create an archive containing the agent class and the manifest file with the contents:

     Premain-class: --
          
          



  3. Create an executable class for evaluating memory.
  4. Specify the archive with the "-javaagent" parameter when starting the virtual machine:

     java -javaagent:- --
          
          







Let's start with a test case. For simplicity we use an unnamed package.



Step 1. Create a trial agent class



 import java.lang.instrument.Instrumentation; public class A { public static void premain(String notUsedHere, Instrumentation i) { System.out.println("premain"); } }
      
      





We compile:



 javac A.java
      
      





Step 2. Create the m.txt manifest file containing:



 Premain-class: A  
      
      





ATTENTION: the second line of the file must be EMPTY, NOT CONTAINING SPACES.



Create the A.jar archive:



 jar cmf m.txt A.jar A.class
      
      





Step 3. Create a trial executable class



 public class M { public static void main(String[] notUsedHere) { //     System.out.println("main"); } }
      
      





We compile:



 javac M.java
      
      





Step 4. Perform



 java -javaagent:A.jar M
      
      





Result:
 premain main
      
      





indicates that the premain method of the agent class was called first, and then the main method of the executable class.



Now create the required agent class:



 import java.lang.instrument.Instrumentation; public class A { //      private static Instrumentation ins; public static void premain(String notUsedHere, Instrumentation i) { ins = i; } public static Instrumentation instrumentation() {return ins;} }
      
      





and executable class:



 class M { public static void main(String[] notUsedHere) { mem("Object", new Object()); } private static void mem(Object o, Object ref) { System.out.println(o + ": " + objectBytesEstimate(ref)); } private static long objectBytesEstimate(Object ref) { if (A.instrumentation() == null) { throw new RuntimeException("Not initialized instrumentation."); } return A.instrumentation().getObjectSize(ref); } }
      
      





Method



 long getObjectSize(Object --)
      
      





returns an ESTIMATION of the size (number of bytes) of memory occupied by the object at the specified link. It must be borne in mind that the resulting estimate may be different for another virtual machine. Values ​​for jdk-13 will be listed here .



We carry out:



 javac *.java jar cmf m.txt A.jar A.class java -javaagent:A.jar M
      
      





and we get the result:



 Object: 16
      
      





indicating that an EMPTY object of type Object occupies here (BY ASSESSMENT) 16 bytes. Of these, 12 bytes occupies the header, and 4 bytes at the end serve to align the length of the object to the boundary of 8 bytes.



results



Further examples will contain only the code placed in the main method of class M. They should be executed for each example with the commands:



 javac M.java java -javaagent:A.jar M
      
      





Re-creating A.jar is not necessary.



For example, to get an estimate of the memory size of an object of an arbitrary type without fields, we put the code in the main method:



 class C {}; mem("Empty", new C()); // Empty: 16
      
      





The result indicated in the comment shows that an object without fields occupies as many bytes as an object of type Object.



Further, the result of the program:



 {class C {int a; } mem(1, new C());} // 1: 16 {class C {int a,b; } mem(2, new C());} // 2: 24 {class C {int a,b,c; } mem(3, new C());} // 3: 24 {class C {int a,b,c,d;} mem(4, new C());} // 4: 32
      
      





indicates that each int field takes 4 bytes. I note that here each line is a separate block, which allows you to use the same name for different classes.



Each long field is 8 bytes:



 {class C {long a; } mem(1, new C());} // 1: 24 {class C {long a,b; } mem(2, new C());} // 2: 32 {class C {long a,b,c;} mem(3, new C());} // 3: 40
      
      





Each boolean-field takes 1 byte (for this VM):



 {class C {boolean a; } mem(1, new C());} // 1: 16 {class C {boolean a,b; } mem(2, new C());} // 2: 16 {class C {boolean a,b,c; } mem(3, new C());} // 3: 16 {class C {boolean a,b,c,d; } mem(4, new C());} // 4: 16 {class C {boolean a,b,c,d,e;} mem(5, new C());} // 5: 24
      
      





Each reference field takes 4 bytes (for this VM):



 {class C {Boolean a; } mem(1, new C());} // 1: 16 {class C {Integer a; } mem(1, new C());} // 1: 16 {class C {Long a; } mem(1, new C());} // 1: 16 {class C {C a; } mem(1, new C());} // 1: 16 {class C {Boolean a,b; } mem(2, new C());} // 2: 24 {class C {Integer a,b; } mem(2, new C());} // 2: 24 {class C {Long a,b; } mem(2, new C());} // 2: 24 {class C {C a,b; } mem(2, new C());} // 2: 24 {class C {Boolean a,b,c; } mem(3, new C());} // 3: 24 {class C {Integer a,b,c; } mem(3, new C());} // 3: 24 {class C {Long a,b,c; } mem(3, new C());} // 3: 24 {class C {C a,b,c; } mem(3, new C());} // 3: 24 {class C {Boolean a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Integer a,b,c,d;} mem(4, new C());} // 4: 32 {class C {Long a,b,c,d;} mem(4, new C());} // 4: 32 {class C {C a,b,c,d;} mem(4, new C());} // 4: 32
      
      





The String type field also takes 4 bytes, like every reference:



 {class C {String a; } mem(" null", new C());} // null: 16 {class C {String a=""; } mem(" empty", new C());} // empty: 16 {class C {String a="A"; } mem("1-char", new C());} // 1-char: 16 {class C {String a="1234567";} mem("7-char", new C());} // 7-char: 16
      
      





An array reference field also takes 4 bytes, like every reference one:



 {class C {int[] a; } mem("null", new C());} // null: 16 {class C {int[] a = {}; } mem(" 0", new C());} // 0: 16 {class C {int[] a = new int[1]; } mem(" 1", new C());} // 1: 16 {class C {int[] a = new int[7]; } mem(" 7", new C());} // 7: 16 {class C {int[][] a = {}; } mem(" 00", new C());} // 00: 16 {class C {int[][] a = new int[1][1];} mem(" 11", new C());} // 11: 16 {class C {int[][] a = new int[7][7];} mem(" 77", new C());} // 77: 16
      
      





The subtype object contains each field declared in the superclass, regardless of the access modifier:



 {class S { } class C extends S {long a;} mem("0+1", new C());} // 0+1: 24 {class S {private long a;} class C extends S { } mem("1+0", new C());} // 1+0: 24
      
      





The subtype object contains a field declared in the superclass with the same name as in the subclass (the so-called hidden - hidden):



 {class S { } class C extends S {long a,b;} mem("0+2", new C());} // 0+2: 32 {class S {long a;} class C extends S {long a; } mem("1+1", new C());} // 1+1: 32
      
      





A subtype object contains each field declared in each of its superclasses:



 class U {private long a; } class S extends U {private long a; } class C extends S { long a; } mem("1+1+1", new C()); // 1+1+1: 40 class D { long a,b,c;} mem("0+0+3", new D()); // 0+0+3: 40
      
      





Turn to arrays. As you know, an array is a special kind of object whose elements are in the object itself, so the size of the memory occupied by the array grows with the number of elements:



 {long[] a = new long[ 0]; mem(" 0", a);} // 0: 16 {long[] a = new long[ 1]; mem(" 1", a);} // 1: 24 {long[] a = new long[ 2]; mem(" 2", a);} // 2: 32 {long[] a = new long[ 3]; mem(" 3", a);} // 3: 40 {long[] a = new long[100]; mem("100", a);} // 100: 816
      
      





And for the array of links:



 {Long[] a = new Long[ 0]; mem(" 0", a);} // 0: 16 {Long[] a = new Long[ 1]; mem(" 1", a);} // 1: 24 {Long[] a = new Long[ 2]; mem(" 2", a);} // 2: 24 {Long[] a = new Long[ 3]; mem(" 3", a);} // 3: 32 {Long[] a = new Long[100]; mem("100", a);} // 100: 416
      
      





Now, out of curiosity, we compare the sizes of several objects of different types:



 mem(" Object", new Object()); // Object: 16 mem(" String", new String("ABC")); // String: 24 mem(" Exception", new Exception()); // Exception: 40 mem(" int.class", int.class); // int.class: 112 mem(" int[].class", int[].class); // int[].class: 112 mem("Object.class", Object.class); // Object.class: 112 mem("System.class", System.class); // System.class: 160 mem("String.class", String.class); // String.class: 136
      
      





The same for different jdk on a 64-bit processor:



                 jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9 jdk-12 jdk-13
                 ----------- ----------- ------------ ------ ------ ---- -
       Object: 16 16 16 16 16 16
       String: 32 24 24 24 24 24
    Exception: 32 32 32 40 40 40
    int.class: 104 88 104 112 104 112
  int []. class: 584 544 480 112 104 112
 Object.class: 600 560 496 112 104 112
 System.class: 624 560 496 144 152 160
 String.class: 696 640 624 136 128 136 


The String size estimate is 24 bytes, although the String class contains many static variables, static and non-static methods. This undoubtedly indicates the absence of static variables and method code in the object. The same is true for an object of any type.



In conclusion - a reminder: all the data on the size of the object are estimated and they can vary to some extent from one execution to another and, of course, for different virtual machines.



All Articles