Home | Blog | RSS

About Sizes in Java

2012-09-26

Recently, I've been pondering the question: "How can one accurately assess the amount of memory allocated for objects in Java?" There are several articles on Habrahabr [1], [2] dedicated to this topic. However, I wasn't quite satisfied with the approach used by the authors. Therefore, I decided to delve into the internals of the OpenJDK Hotspot VM (hereafter referred to as Hotspot) and try to understand how things really work.

Data Types in Java

Primitives

The size of primitives is clear—they are defined in the language specification (JLS 4.2) and the JVM specification (JVMS 2.3). Interestingly, for the boolean type, the JVM uses int, not byte as one might think (JMS 2.3.4). Also, it's noteworthy that when creating a boolean[] array, 1 byte, not 4, is allocated for each element.

TypeSize (bytes)Size in array (bytes)Allowed values
byte11-128 .. 127
short22-32768 .. 32767
char22'\u0000' .. '\uffff'
int44-2147483648 .. 2147483647
float44-3.4028235e+38f .. 3.4028235e+38f
long88-9223372036854775808 .. 9223372036854775807
double88-1.7976931348623157e+308 .. 1.7976931348623157e+308
boolean41false, true

Objects

Hotspot describes array instances using the class arrayOopDesc, and other Java classes using instanceOopDesc. Both classes inherit from oopDesc and contain methods for calculating the size of the header. For instance, instanceOopDesc calculates the header size (in machine words) as follows:

static int header_size() {
  return sizeof(instanceOopDesc)/HeapWordSize;
}

where HeapWordSize is defined as the size of a pointer, which depends on the CPU architecture—4 bytes for x86 and 8 bytes for x86_64 (referred to by Oracle as x64). To understand the size of instanceOopDesc, one must look into oopDesc, as instanceOopDesc itself declares no fields. Here's what we find there:

class oopDesc {
   // ...
	volatile markOop  _mark;
	union _metadata {
		wideKlassOop    _klass;
		narrowOop       _compressed_klass;
	} _metadata;
	// ...
};

In the file [oopsHierarchy.hpp](http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/tip

/src/share/vm/oops/oopsHierarchy.hpp), the necessary data types for working with the oop object hierarchy are declared. Let's look at the types used in oopDesc:

// ...
typedef juint narrowOop; // Offset instead of address for an oop within a java object
typedef class klassOopDesc* wideKlassOop; // to keep SA happy and unhandled
                                          // oop detector happy.
// ...
typedef class markOopDesc*  markOop;
// ...

That is, these are two pointers (read: two machine words) for a specific architecture—the so-called mark word and an address (which can be represented by a pointer or offset) to the class metadata. The idea of this union metadata is that with the option -XX:+UseCompressedOops enabled, a 32-bit offset (_compressed_klass) rather than a 64-bit address (_klass) will be used. Thus, the header size of a Java object is 8 bytes for x86 and 16 bytes for x86_64, regardless of the UseCompressedOops parameter:

Architecture-XX:+UseCompressedOops-XX:-UseCompressedOops
x868 bytes (4 + 4)8 bytes (4 + 4)
x86_6416 bytes (8 + 8)16 bytes (8 + 8)

Arrays

In arrayOopDesc, the header size is calculated as follows:

static int header_size_in_bytes() {
    size_t hs = align_size_up(length_offset_in_bytes() + sizeof(int), HeapWordSize);
    // ...
    return (int)hs;
}

where:

Let's calculate what we get. The header size of an array after alignment:

Architecture-XX:+UseCompressedOops-XX:-UseCompressedOops
x8612 bytes (4 + 4 + 4 align 4)12 bytes (4 + 4 + 4 align 4)
x86_6416 bytes (8 + 4 + 4 align 8)24 bytes (8 + 8 + 4 align 8)

Alignment

To prevent scenarios of cache line false sharing (false sharing), the size of an object in Hotspot is aligned to an 8-byte boundary. This means that even if an object occupies just 1 byte, 8 bytes will be allocated for it. The boundary size is chosen such that the cache line is a multiple of this boundary, and this boundary must be a power of two and a multiple of the machine word. Since most modern processors have a cache line size of 64 bytes and a machine word size of 4/8 bytes, the boundary size was chosen to be 8 bytes. In the file globalDefinitions.hpp (lines 372 - 390), corresponding definitions are available. I won't include them here; those interested can go and look.

Starting with version jdk6u21, the alignment size became a configurable parameter. It can be set using the parameter -XX:ObjectAlignmentInBytes=n. Permissible values are 8 and 16.

And what do we get?

Here's the layout (for x86_64):

public class Point {                         // 0x00 +------------------+
    private int x;                           //      | mark word        |  8 bytes
    private int y;                           // 0x08 +------------------+
    private byte color;                      //      | klass oop        |  8 bytes
                                             // 0x10 +------------------+
    public Point(int x, int y, byte color) { //      | x                |  4 bytes
        this.x = x;                          //      | y                |  4 bytes
        this.y = y;                          //      | color            |  1 byte
        this.color = color;                  // 0x19 +------------------+
    }                                        //      | padding          |  7 bytes
                                             //      |                  |
    // ...                                   // 0x20 +------------------+
}                                            //                    total: 32 bytes

For an array of char[] with 11 elements (for x86_64):

char[] str = new char[] {                    // 0x00 +------------------+
    'H', 'e', 'l', 'l', 'o', ' ',            //      | mark word        |  8 bytes
    'W', 'o', 'r', 'l', 'd' };               // 0x08 +------------------+
                                             //      | klass oop        |  4 bytes
                                             // 0x0c +------------------+
                                             //      | length           |  4 bytes
                                             // 0x10 +------------------+
                                             //      | 'H'              | 22 bytes
                                             //      | 'e'              |
                                             //      | 'l'              |
                                             //      | 'l'              |
                                             //      | 'o'              |
                                             //      | ' '              |
                                             //      | 'W'              |
                                             //      | 'o'              |
                                             //      | 'r'              |
                                             //      | 'l'              |
                                             //      | 'd'              |
                                             // 0x26 +------------------+
                                             //      | padding          |  2 bytes
                                             // 0x28 +------------------+
                                             //                    total: 40 bytes

Further Reading