Understanding Java Object Memory Layout
This article provides a detailed introduction to the storage structure of Java objects in memory, discusses how to reasonably estimate the approximate memory space required for project operation through tools and theoretical knowledge, and deepens the understanding of the Java object memory structure.
1. Overview of JVM Memory Structure
The JVM memory structure is mainly divided into the following parts:
- Stack Memory: Used to store local variables, operand stacks, and method return addresses during method calls. It has a short lifecycle and is released when the method execution ends.
- Heap Memory: Stores all object instances and arrays and is the main area for garbage collection.
- Method Area: Stores class structure information, constants, static variables, etc. It is called “Metaspace” in JDK 8 and later.
- Native Method Stack: Serves the execution of native methods by the JVM, similar to stack memory but specifically for native methods.
- Program Counter: Records the address of the bytecode being executed by each thread.
Since heap memory is the main area for storing object instances, this article will focus on the management of heap memory and the storage structure of Java objects.
2. Memory Layout of Java Objects
The memory structure of a Java object is divided into three parts:
- Object Header: Contains the Mark Word, class pointer, and array length.
- Instance Data: Stores the non-static member variables of the object.
- Padding: Ensures that the object’s storage address is aligned to a certain number of bytes.
1. Object Header
The object header consists of three parts:
- Mark Word: Occupies 4 bytes in a 32-bit JVM and 8 bytes in a 64-bit JVM. It stores information such as the object’s GC generation age, lock flag, bias lock status, thread ID, timestamp, and hash value.
- Class Pointer: Points to the class information of the object, facilitating access to the class information corresponding to the object. It occupies 4 bytes or 8 bytes, depending on whether pointer compression is enabled.
- Array Length: Present only in array objects, indicating the array length and occupying 4 bytes.
2. Instance Data
The instance data stores the non-static member variables of the object, including both primitive and reference types. The byte lengths of different types are as follows:
Type | Byte Size |
---|---|
double | 8 |
long | 8 |
float | 4 |
int | 4 |
short | 2 |
char | 2 |
byte | 1 |
boolean | 1 |
Reference | 8 (4 when compressed) |
3. Padding
To improve CPU memory access efficiency, the storage of objects in memory must be aligned to a certain number of bytes. For a 64-bit JVM, this is usually 8-byte alignment. If the object size is not a multiple of 8, padding bytes are used to fill the gap.
3. Using the JOL Tool
To view the memory layout of Java objects, we can use the JOL (Java Object Layout) tool. JOL can be included in your project via Maven or Gradle:
// Maven
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
<scope>provided</scope>
</dependency>
// Gradle
implementation 'org.openjdk.jol:jol-core:0.16'
Here is an example of using the JOL tool to view the memory layout of an object:
// MyObject.java
public class MyObject {
private long b;
private int a;
private Integer e;
private static final int s = 1;
public void f() {}
//...methods omitted...
}
import org.openjdk.jol.info.ClassLayout;
public class Demo9_1 {
public static void main(String[] args) {
System.out.println(
ClassLayout.parseInstance(new MyObject()).toPrintable());
}
}
Running the above code displays the memory layout of an object of the MyObject
class. The output includes the offset address (OFFSET), size (SIZE), type (TYPE), and value (VALUE) of each field.
4. Detailed Analysis of the Object Header
The object header includes the Mark Word, class pointer, and array length.
1. Mark Word
The Mark Word occupies 4 bytes in a 32-bit JVM and 8 bytes in a 64-bit JVM. It stores information such as:
- GC generation age (age)
- Lock flag (lock)
- Bias lock status (biased_lock)
- Thread ID (thread)
- Timestamp (epoch)
- Hash value (hashcode)
Most of the information recorded in the Mark Word is used for multithreading and JVM garbage collection.
2. Class Pointer
The class pointer points to the class information in the method area, facilitating access to the class information corresponding to the object. In a 64-bit JVM, the class pointer occupies 8 bytes, but it can be reduced to 4 bytes through pointer compression.
3. Array Length
The header of array objects contains an array length field, which occupies 4 bytes. The JVM treats arrays as a special kind of object, with a memory layout similar to regular objects, but with the addition of the array length field.
5. Storage of Instance Data
Instance data stores the non-static member variables of the object, including both primitive and reference types. The memory address of each attribute of the object must be a multiple of its byte length. This storage requirement is called “byte alignment,” and padding bytes are used to fill any gaps.
Storage Order of Object Attributes
The attributes in an object are not stored in the order in which they are defined. The JVM arranges the storage order of attributes according to the following rules:
- Rule 1: First store the attributes of the superclass, then store the attributes of the subclass.
- Rule 2: Attributes in the class are stored in the following order: double/long, float/int, short/char, byte/boolean, object reference.
- Rule 3: The storage address of any attribute is aligned and filled according to the byte length of its type.
- Rule 4: There is a 4-byte alignment between the attributes of the superclass and subclass. If it is not 4 bytes, it will be filled to 4 bytes.
- Rule 5: If there is a gap between the attributes of the superclass and subclass, use the attributes of the subclass to fill the gap.
The following example demonstrates the application of the above rules:
public class A {
private char a;
private long b;
private float c;
//...methods omitted...
}
public class B extends A {
private boolean a;
private char b;
private long c;
private String d = "abc";
//...methods omitted...
}
public class Demo9_1 {
public static void main(String[] args) {
System.out.println(
ClassLayout.parseInstance(new B()).toPrintable());
}
}
Running the above code shows the memory layout of an object of class B
.
6. Importance of Padding
Padding is to improve CPU memory access efficiency. A 64-bit CPU reads 8 bytes of data from memory each time, and the JVM must store long types, double types, reference types, and other 8-byte data in aligned 8-byte memory blocks. If not aligned, it may cause cross-memory block reading, increasing the CPU burden and affecting performance.
For data types smaller than 8 bytes, the JVM aligns according to the type length without needing 8-byte alignment.
7. Compressed Class Pointers and References
To save memory, the JVM enables class pointer compression (-XX:+UseCompressedClassPointers
) and reference compression (-XX:+UseCompressedOops
) by default. Through compression, the pointers and references in a 64-bit JVM can be reduced from 8 bytes to 4 bytes.
Compression Principle
Compressed reference type attributes occupy only 4 bytes, storing the memory address of the object. Four bytes can address 4GB of memory, and through shifting, it can expand to 32GB. If more heap memory is needed, the object alignment length can be increased (-XX:ObjectAlignmentInBytes
).
For example, with default 8-byte alignment, the maximum heap memory is 32GB; with 16-byte alignment, the maximum heap memory is 64GB, and so on.
8. Instance Analysis and Calculation
By using actual examples and the JOL tool to analyze the memory layout of objects, we can verify the application of byte alignment and padding rules.
Example Classes
public class A {
private char a;
private long b;
private float c;
private static final int d = 1;
}
public class C {
private int a;
private char b;
private A c;
private double d;
}
public class D extends C {
private boolean a;
private long b;
private char c;
}
Calculating the Memory Occupation of Class A
- Object header: 12 bytes (Mark Word + class pointer)
- Instance data: 1 byte (char a) + 3 bytes (padding) + 8 bytes (long b) + 4 bytes (float c) = 16 bytes
- Total: 12 bytes + 16 bytes = 28 bytes
- Padding: 8-byte alignment, total 32 bytes
Calculating the Memory Occupation of Class C
- Object header: 12 bytes (Mark Word + class pointer)
- Instance data: 4 bytes (int a) + 2 bytes (char b) + 2 bytes (padding) + 4 bytes (reference to A c) + 8 bytes (double d) = 20 bytes
- Total: 12 bytes + 20 bytes = 32 bytes
Calculating the Memory Occupation of Class D
- Object header: 12 bytes (Mark Word + class pointer)
- Superclass instance data: 20 bytes (inherited from C)
- Subclass instance data: 1 byte (boolean a) + 7 bytes (padding) + 8 bytes (long b) + 2 bytes (char c) = 18 bytes
- Total: 12 bytes + 20 bytes + 18 bytes = 50 bytes
- Padding: 8-byte alignment, total 56 bytes
Therefore, the total memory space occupied by an object of class D is 56 bytes.
9. Summary
This article provides a detailed introduction to the object header, instance data, and padding, and verifies the application of byte alignment and padding rules through practical examples. Through the study of this article, one should be able to better understand and optimize JVM memory configuration, thereby improving the performance and stability of Java applications.