JOL(Java Object layout)
⚠⚠⚠ This article takes Java common objects as the pointcut, analyzes the Java object memory layout, array see the end of the article
Maven address 👇👇👇, use version 0.9, the new version of the printed information is simplified
<! -- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core --> <dependency> <groupId>org.openjdk.jol</groupId> < artifactId > jol - core < / artifactId > < version > 0.9 < / version > < scope > provided < / scope > < / dependency >Copy the code
1. Calculate the size of an object (in bytes) using jol:ClassLayout.parseInstance(obj).instanceSize() 2. Using jol view object memory layout: ClassLayout parseInstance (obj). ToPrintable ()Copy the code
Basic concepts:
Question 1. How do Java objects be stored?
The instance of the object (instantOopDesc) is stored on the heap, and the object’s metadata (instantKlass, i.e. class file) is stored in the method area (metadata?). , the reference to the object is kept on the stack.
Problem 2: Pointer compression
Enabling pointer compression reduces the memory usage of an object.
When pointer compression is disabled, fields such as String and Integer occupy 8 bytes each because they are reference types.
With pointer compression enabled, these two fields only take up 4 bytes each.
Thus, turning on pointer compression could, in theory, save nearly 50 percent of memory. (if the object properties are all reference types)
The JDK8 and later versions have enabled pointer compression by default.
Pointer compression is tested below
- Enable (-xx :+UseCompressedOops) to compress Pointers.
- Closing (-xx: -usecompressedoops) closes the compression pointer.
1. Empty properties – Object layout
Define a simple Java object and print its memory layout
import org.openjdk.jol.info.ClassLayout;
public class Entity {
public static void main(String[] args) {
Entity entity = new Entity();
// Print the Java object memory layoutSystem.out.println(ClassLayout.parseInstance(entity).toPrintable()); }}// Output the result
com.brown.Entity object internals: // Entity object memory layout
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) 05 c1 00 f8 (00000101 11000001 00000000 11111000) (-134168315)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Copy the code
- OFFSET: indicates the OFFSET address, in bytes
- SIZE: indicates the memory SIZE, in bytes
- TYPE DESCRIPTION: Type description, where
object header
Is the object header;
- Object Header: Indicates the object header
- Loss due to the next object alignment: Loss due to alignment of the next object (4 bytes are aligned bytes (because the size of an object must be a multiple of 8 on a 64-bit virtual machine), and since there are no fields in this object, the instance data of the object is 0 bytes).
- VALUE: corresponds to the VALUE currently stored in memory;
- (** Here an empty Java object (
Does not contain any field properties
(example size: ‘ ’16 bytes六四运动
)
Analysis:
The header of an Entity object (which does not contain attribute fields) takes 12 bytes when pointer compression is enabled.
Every time a new object is created while a Java program is running, the JVM accordingly creates an OOP object of the corresponding type and stores it in the heap. For example, a new Entity() creates an instanceOopDesc with the base class oopDesc.
[instanceOop HPP files: the hotspot/SRC/share/vm/oops/instanceOop HPP]class instanceOopDesc : public oopDesc {
}
Copy the code
InstanceOopDesc provides only a few static methods, such as getting the object header size. So focus on the parent oopDesc.
[oop. HPP files: the hotspot/SRC/share/vm/oops/oop HPP]class oopDesc {
friend class VMStructs;
private:
volatile markOop _mark;
union _metadata {Klass* _klass; narrowKlass _compressed_klass; } _metadata; . }Copy the code
We only care about the object header. The object header for a normal object (such as an Entity object, not an array type) consists of a markOop and a union. A markOop is a MarkWord. This union is a metadata pointer to the Class. When pointer compression is disabled, use _klass. When pointer compression is enabled, use _compressed_klass.
MarkOop with narrowKlass type definitions in the hotspot/SRC/share/vm/oops/oopsHierarchy HPP header file:
[oopsHierarchy HPP header file: / hotspot/SRC/share/vm/oops/oopsHierarchy HPP]typedef juint narrowKlass;
typedef class markOopDesc* markOop;
Copy the code
Therefore, narrowKlass is a juint, and junit is defined in the globaldefinitions_viscp.hpp header, which is an unsigned integer of 4 bytes. So with pointer compression enabled, the size of the pointer to the Klass object is 4 bytes.
[/hotspot/src/share/vm/utilties/globalDefinitions_visCPP.hpp]
typedef unsigned int juint;
Copy the code
MarkOop is a pointer of type markOopDesc, which is a MarkWord. In case you’re wondering, on 64-bit JVMS, the markOopDesc pointer is 8 bytes, or 64bit. It’s exactly the size of MarkWord, but isn’t it pointing to an object? Let’s start with the markOopDesc class.
[markOop HPP files: the hotspot/SRC/share/vm/oops/markOop HPP]// 64 bits:
// --------
// unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
// PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
// size:64 ----------------------------------------------------->| (CMS free block)
class markOopDesc: public oopDesc {
......
}
Copy the code
The MarkWord 64-bit storage information is described in the markoop. HPP header. The markOopDesc class also inherits oopDesc. If you look at the source code of the markOopDesc class, you won’t find it. MarkOopDesc uses that field to store MarkWord. Also, what we know from various sources is that the first 8 bytes of the object header store information such as biased locking, lightweight locking, etc. (the whole text is 64-bit), so it should not be a pointer.
To answer this question, I first look at the source code for the markOopDesc class and find a method, for example, to get the age of a GC object, to see where the JVM is getting the data from.
class markOopDesc: public oopDesc {
public:
// Get the age of the object
uint age(a) const {
return mask_bits(value() >> age_shift, age_mask);
}
// Update the age of the object
markOop set_age(uint v) const {
return markOop((value() & ~age_mask_in_place) | (((uintptr_t)v & age_mask) << age_shift));
}
// Increase the age of the object
markOop incr_age(a) const {
return age() == max_age ? markOop(this) : set_age(age() + 1); }}Copy the code
The value() method returns the 64-bit MarkWord.
class markOopDesc: public oopDesc {
private:
// Conversion
uintptr_t value(a) const { return (uintptr_t) this; }}Copy the code
The value method returns a pointer to this. You can also see from the set_AGE and incr_age methods that whenever you modify MarkWord, a new markOop (markOopDesc*) is returned. No wonder markOopDesc is defined as markOop, using it as an 8-byte integer. To understand this, we need a little bit of c++, so I wrote a demo.
Analysis of the demo
Customize a class called oopDesc and provide only a Show method in addition to the constructor and destructor.
[.] HPP file#ifndef oopDesc_hpp
#define oopDesc_hpp
#include <stdio.h>
#include <iostream>
using namespace std;
// Define oopDesc* as OOP
typedef class oopDesc* oop;
class oopDesc{
public:
void Show(a);
};
#endif /* oopDesc_hpp */[.] CPP file#include "oopDesc.hpp"
void oopDesc::Show(a){
cout << "oopDesc by wujiuye" <<endl;
}
Copy the code
Create an oopDesc* using OOP (pointer) and call the show method.
#include <iostream>
#include "oopDesc.hpp"
using namespace std;
int main(int argc, const char * argv[]) {
oopDesc* o = oop(0x200);
cout << o << endl;
o->Show(a);return 0;
}
Copy the code
Test output
0x200
oopDesc by wujiuye
Program ended with exit code: 0
Copy the code
Therefore, with the class name (value), you can create an wild pointer object, assign the pointer to value, and use this as a MarkWord. If you add a field to oopDesc and provide a method access, the program will run with an error, so the object created this way can only call methods, not fields.
Conclusion:
The object layout consists of three parts:
- Object header the front of the object header
64
A (8byte
) isMarkWord
After,32
A (4byte
Is a metadata pointer to the class (with pointer compression enabled). 】 - The instance data
- Byte alignment (optional, if object header plus instance data is a multiple of 8, there is no byte alignment)
Hotspot 64-bit implementation
Lock status description in Mark Word (based on the last three digits)
Bias lock bit 1bit (bias lock or not) | The lock flag bit is 2 bits | The lock state |
---|---|---|
0 | 01 | Unlocked state (new) |
1 | 01 | Biased locking |
– | 00 | Lightweight lock (spin lock, no lock, adaptive spin lock) |
– | 10 | Heavyweight lock |
– | 11 | The GC tag |
mark word
Description of object headers in hotspot
// 32 bits: indicates a 32-bit operating system
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
//
// 64 bits: indicates a 64-bit operating system
// --------
/ / unused: 25 hash: 31 - > | unused: 1 age: 4 biased_lock: 1 lock: 2 (normal object) / / no lock
JavaThread*:54 epoch:2 unused:1 age:4 BIased_lock :1 lock:2 (biased object) JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object
/ / PromotedObject * : 61 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > | promo_bits: 3 -- -- -- -- - > | (CMS promoted object) / / lightweight, heavyweight lock lock
/ / size: 64 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - > | (CMS free block) total length
Copy the code
For 32-bit OS object header information, refer to the end of the article!!
The following is the description of the object header for the 64-bit operating system:
|———————————————————————————————————————– —————|
Object Header (128 bits)
|———————————————————————————————————————– —————|
(64 bits) | | Mark Word Klass Word (64 bits) | open the default pointer compression (32 bits) |———————————————————————————————————————– —————| |unused:25|identity_hashcode:31(56) | unused:1 | age:4 | biased_lock:1 | lock:2 | OOP to metadata The object | unlocked |———————————————————————————————————————– — — — — — — — — — — — — — — – | | thread: 54 | epoch: 2 | unused: 1 | age: 4 | biased_lock: 1 the lock: | 2 | OOP to the metadata object | biased locking |———————————————————————|————————————————- — — — — — — — — — — — — — — – | | ptr_to_lock_record: the lock: 62 | 2 | OOP to the metadata object | lightweight lock |———————————————————————————————————————– — — — — — — — — — — — — — — – | | ptr_to_heavyweight_monitor: the lock: 62 | 2 | OOP to the metadata object lock | weight |————————————————————————————————————| | | lock:2 | OOP to metadata object | GC |———————————————————————————————————————– —————|
Mark Word is used to store run-time record information about an object, such as hash value, GC generational age, lock status flag, thread-held lock, biased thread ID, biased timestamp, and so on:
2. There are property-object layouts
/** * Entity class with attributes */
public class Student {
private String name;
private Integer age;
}
Copy the code
import org.openjdk.jol.info.ClassLayout;
public class Entity {
public static void main(String[] args) {
Student o = newStudent(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); }}// Output results (with pointer compression enabled by default) :
com.brown.Student 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.Integer Student.age 0
16 4 java.lang.String Student.name null
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
[(-xx: -usecompressedoops)] :
com.brown.Student 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) 30 35 64 1c (00110000 00110101 01100100 00011100) (476329264)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 8 java.lang.String Student.name null
24 8 java.lang.Integer Student.age null
Instance size: 32 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Copy the code
Pointer compression is optimized for memory
The output shows that when pointer compression is enabled, the memory occupied by the object is 24 bytes. When pointer compression is disabled, the memory occupied by the object is 32 bytes, saving 25% of the memory
Object header size change:
When pointer compression is off, the metadata pointer is Klass type and takes 8 bytes, whereas when pointer compression is on, the metadata pointer is Klass type and takes 8 bytes (analyzed above)
3. About the lock-object layout
Synchronized is used as an example to analyze the storage of object lock information in MarkWord
import org.openjdk.jol.info.ClassLayout;
public class Entity {
public static void main(String[] args) {
Entity entity = new Entity();
// Print the Java object memory layout
synchronized(entity){ System.out.println(ClassLayout.parseInstance(entity).toPrintable()); }}}// Output:
com.brown.Entity object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 f5 4c 02 (11111000 11110101 01001100 00000010) (38598136)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 28 30 9c 1b (00101000 00110000 10011100 00011011) (463220776)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Copy the code
As you can see, the first line of MarkWord results is now printed, already and 1. Empty properties – The output in the layout of the object is different.
The MarkWordk value is 0x0000 0000 024c F5F8, and the binary value is 0xB00000000 00000000 00000000 00000010 01001100 11110101 11111000.
The third-to-last bit is “0”, indicating that the state is not biased lock, and the last-to-last two bits are “00”, therefore, the state is lightweight lock, so the first 62 bits are Pointers to the lock record in the stack.
In addition, you can see that objects are locked when Synchronized code blocks are executed
????? :
What is calculated is the exact opposite of what is output. Here involves a knowledge point “big end storage and little end storage” (assembly language).
- Little-endian: The low byte is stored at the low address end of the memory, and the high byte is stored at the high address end of the memory.
- Big-endian: The high byte is stored at the low address end of the memory, and the low byte is stored at the high address end of the memory.
Lock escalation
Lock state flow:
Object headers in a 32-bit operating system
Biased_lock: indicates the bias lock bit. Lock: indicates the lock status bit. JavaThread* : indicates the ID of the thread holding the bias lock
The different lock bits in markOop represent different lock states:
The layout of arrays in memory
Reference:
-
Concurrent programming —-4, object header detail
-
JVM Learning Note 4 – Synchronized lock status
-
JAVA object header parsing
-
www.bilibili.com/video/BV1xK…