Java, as an object-oriented language, gives us features such as polymorphism, inheritance and encapsulation, which make it easy to build code that is easy to extend and maintain.
As a Javaer working on “objects” all day, how much memory do you use to write objects?
Let’s see how your “date” is a loser.
This article environment: JDK1.8_64
Java object header memory model
Let’s start with, what does the memory model of a Java object look like?
Since our virtual machines are 32-bit and 64-bit, there must be a difference between their models. Below I list the Java object header memory models for 32-bit and 64-bit virtual machines.
Since the author’s local environment is JDK1.8, a 64-bit vm, I will use the 64-bit vm (with pointer compression enabled) for this analysis, because by default, JDK1.8 enables pointer compression on a 64-bit VM.
The Java object header consists of two main parts, the first part is the Mark Word, which is an important part of the Java lock implementation principle, and the other part is the Klass Word.
Klass Word is an OOP-Klass model designed by a virtual machine. Oop refers to an Ordinary Object Pointer that looks like a Pointer but is actually an Object hidden inside a Pointer. Klass, on the other hand, contains metadata and method information that describes Java classes. It takes up 32bits of space in 64-bit virtual machines with compressed Pointers enabled.
Mark Word is the focus of our analysis, and lock related knowledge will also be designed here. Mark Word occupies 64bits of space in a 64-bit VIRTUAL machine environment.
There are several cases of Mark Word distribution:
- Unlocked (Normal) : 31bits of identity_hashcode, 4 bits of generational age, 1bits of biased_lock, 2 bits of lock, 26bits unused (all zeros)
- Biased: the thread id takes up 54bits, the epoch takes up 2 bits, the age takes up 4bits, the Biased mode (biased_lock) takes up 1bit, the lock flag (lock) takes up 2 bits, and the remaining 1bit is unused.
- Lightweight Locked: the lock pointer occupies 62bits and the lock flag occupies 2bits.
- Double feature Lock: Lock pointer takes up 62bits, lock tag takes up 2bits.
- GC flag: mark 2bits, empty (i.e., fill 0)
This is how we parse the Java object header memory model. As long as Java objects are included, they must include object headers, which means that this part of the memory footprint is unavoidable.
So, in my 64-bit virtual machine, Jdk1.8 (with pointer compression enabled), any object that does nothing but declare a class has a memory footprint of at least 96bits, or at least 12 bytes.
Verify the model
Let’s write some code to verify the above memory model. We recommend the JoL tool in the OpenJDK, which can help you to check the memory usage of your objects.
First add maven dependencies
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>Copy the code
Let’s see, how much space does it take to create a normal class without adding any attributes?
/**
* @description:
* @author: luozhou
* @create: 2020-02-26 10:00
**/
public class NullObject {
}Copy the code
According to our previous Analysis of the Java object memory model, an empty object, that is, a single object header, takes 96 bits, or 12 bytes, under pointer compression.
Run the tool to check space usage
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new NullObject()).toPrintable());
}Copy the code
This line of code will parse how much memory you’re using to create a NullObject object. Let’s see what happens:
Here we find that the result shows: Instance size: 16 bytes, the result is 16 bytes, we predicted 12 bytes is different, why is this?
We see that there are three object headers in the figure above, each of which takes up 4 bytes, so the header is 12 bytes. This is consistent with our calculation. The last line is filled with 4 bytes by the VIRTUAL machine.
Memory alignment
To understand why a virtual machine fills 4 bytes, what do we need to know about memory alignment?
We programmers look at memory like this:
The figure above shows a pit a radish memory read way. But the CPU doesn’t actually read and write to memory byte by byte. In contrast, the CPU reads the memory block by block, block size can be 2, 4, 6, 8, 16 bytes, and so on. Block size is what we call memory access granularity.
The diagram below:
Given a 32-bit CPU, it reads chunks of memory in 4-byte granularity. So why do you need memory alignment?
There are two main reasons:
- Platform (portability) Reason: Not all hardware platforms can access arbitrary data at arbitrary addresses. For example, certain hardware platforms only allow certain types of data to be retrieved at certain addresses. Otherwise, exceptions may occur.
- Performance reason: Accessing unaligned memory will cause the CPU to make two memory accesses and spend extra clock cycles processing alignment and computation. Aligned memory requires only one access to complete the read action.
I use a legend to illustrate the process of CPU access non-memory alignment:
In the figure above, suppose the CPU reads 4 bytes at a time. In this contiguous 8-byte memory space, if my data is misaligned and the memory block is stored at addresses 1,2,3,4, the CPU read will require two reads, plus additional computation:
- The CPU first reads the first block of memory with an unaligned address, reading 0-3 bytes. And removes the unwanted byte 0.
- The CPU reads the second memory block of the unaligned address again, reading 4-7 bytes. Remove unwanted bytes 5, 6, and 7.
- Merge 1-4 bytes of data.
- Put into register after merge.
So, failure to align memory results in additional CPU reads and additional computation.
If memory alignment is done, the CPU can read directly from address 0, and the desired data is read in one time, no additional read operations and operations, saving running time. We traded space for time, which is why we need memory alignment.
Back to the Java empty object filling 4 bytes, because the original byte header is 12 bytes, on 64-bit machines, memory alignment is 128 bits, which is 16 bytes, so we need to fill 4 more bytes.
Non-empty objects occupy memory calculation
We know that an empty object takes up 16 bytes. How many bytes does a non-empty object take up?
Let’s write a common class to verify this:
public class TestNotNull {
private NullObject nullObject=new NullObject();
private int a;
}Copy the code
Int takes up 4 bytes,NullObject takes up 16 bytes,NullObject takes up 12 bytes, and it’s important that NullObject is a reference in this class, so it doesn’t store the actual object, it just stores the reference address. The reference address is 4 bytes, so the total is 12+4+4=20 bytes, or 24 bytes after memory alignment.
Let’s verify this:
Public static void main (String [] args) {/ / print instance memory layout System. Out. The println (ClassLayout. ParseInstance (new TestNotNull()).toPrintable()); / / print the object of all relevant memory footprint System. Out. The println (GraphLayout. ParseInstance (new TestNotNull ()). ToPrintable ()); / / print the object of all memory results and statistical System. Out. The println (GraphLayout. ParseInstance (new TestNotNull ()). ToFootprint ()); }Copy the code
The results are as follows:
We can see that TestNotNull’s class occupies 24 bytes, including 12 bytes for the header, 4 bytes for the variable A, 4 bytes for the variable nullObject, 4 bytes for the reference, and 4 bytes for the final fill, making a total of 24 bytes, as we predicted earlier.
However, since we instantiated NullObject, the object will be in memory for a while, so we also need to add 16bytes to the memory footprint of the object, which adds up to 24bytes+16bytes=40bytes. The final statistical print result in our figure is also 40 bytes, so our analysis is correct.
This is also how to analyze how much memory an object really takes up, according to this idea and the openJDK jol tool can basically understand how much memory you write “object” is losing you.
conclusion
This article focuses on how to analyze how much memory a Java object occupies. The main points are as follows:
- Java object header memory models are different for 32-bit VMS and 64-bit VMS. 64-bit VMS have pointer compression enabled and not enabled, so there are three object header models in total.
- Memory alignment is primarily for platform reasons and performance reasons, and this article focuses on performance reasons.
- Memory alignments are calculated for empty objects, and memory alignments for non-empty objects are calculated for reference objects and original instance objects.
Wenyuan network, only for the use of learning, such as infringement, contact deletion.
I will be high quality technical articles and experience summary are collected in my public account [Java circle], for the convenience of everyone to learn, but also organized a set of learning materials, free to love Java students! More learning communication group, more communication problems can be faster progress ~