Detailed explanation of java object data structure

I have been confused about what is stored in Java objects before, after a period of learning. Come and summarize today. Hope this helps anyone who sees it.

1. Overall overview

In the HotSpot virtual machine, the layout of objects stored in memory can be divided into three areas: object header (Header), instance data (Instance Data) and alignment padding (Padding) . The following figure shows the data structure of common object instances and array object instances:
insert image description here

object header

The object header of the HotSpot virtual machine includes two parts of information:

  1. Mark Word
    MarkWordIt is used to store the runtime data of the object itself, such as hash code (HashCode), GC generational age, lock status flag, lock held by the thread, biased thread ID, biased timestamp, etc. The length of this part of data is 32 bits and 64-bit virtual machines (compression pointers are not turned on) are 32bit and 64bit respectively, and the official name is "MarkWord".
  2. Klass Word
    Klass WordIt is a klass type pointer, that is, the pointer of the object to its class metadata, and the virtual machine uses this pointer to determine which class the object is an instance of.
  3. Array length (only for array objects).
    If the object is an array, there must be a piece of data in the object header to record the length of the array.

instance data

  • instance dataIt is the effective information actually stored by the object, and it is also the content of various types of fields defined in the program code. Whether it is inherited from the parent class or defined in the subclass, it needs to be recorded.

Align padding

  • Align paddingIt does not necessarily exist, and it has no special meaning, it just acts as a placeholder. Because the automatic memory management system of HotSpot VM requires that the starting address of the object must be an integer multiple of 8 bytes, in other words, the size of the object must be an integer multiple of 8 bytes. The object header part is exactly a multiple of 8 bytes (1 or 2 times), therefore, when the object instance data part is not aligned, it needs to be completed by alignment padding. (In fact, it is mainly to reduce garbage debris)

understand deeper

Knowing the overall structure of the object, let's take a deeper look at the three parts of the object header.

Mark Word

The object header markword is described in the source code of openjdk as follows:
insert image description here
translate the content of the source code to get the following icon form, and then we will explain this table in detail.
insert image description here
In this table, each row is what an object looks like in a certain state.

lock: 2-bit lock status flag, because it is hoped to use as few binary bits as possible to represent as much information as possible, so the lock flag is set. The value of the mark is different, and the meaning of the whole Mark Word is different. biased_lock and lock together express the meaning of the lock state as follows:
biased_lock: whether the object is enabled with a biased lock flag, only occupying 1 binary bit. When it is 1, it means that the object has a biased lock enabled, and when it is 0, it means that the object has no biased lock. lock and biased_lock together indicate what lock state the object is in.

age: 4-digit Java object age. In GC, if the object is copied once in the Survivor area, the age is increased by 1. When an object reaches a set threshold, it is promoted to the old generation. By default, the age threshold for parallel GC is 15 and the age threshold for concurrent GC is 6. Since age has only 4 bits, the maximum value is 15, which is why the maximum value of the -XX:MaxTenuringThreshold option is 15.

identity_hashcode: 31-bit object identifier hashCode, using lazy loading technology. Call the method System.identityHashCode() to calculate and write the result to the object header. When the object is locked (biased, lightweight, heavyweight), the bytes of MarkWord do not have enough space to save the hashCode, so the value will be moved to the monitor.

thread: ID of the thread holding the biased lock.

epoch: The timestamp of the biased lock.

ptr_to_lock_record: In the lightweight lock state, the pointer to the lock record in the stack.

ptr_to_heavyweight_monitor: In the heavyweight lock state, the pointer to the object monitor Monitor.

Leave a more intuitive picture.
insert image description here
The real name of the synchronization lock implemented by synchronized is called a heavyweight lock. However, heavyweight locks will cause threads to queue up (serial execution), and will cause the CPU to frequently switch between user mode and core mode, so the cost is high and the efficiency is low. In order to improve efficiency, heavyweight locks will not be used from the beginning, and the JVM will internally upgrade the locks as needed.

Here we first explain these types of locks:

Classification of locks:

  1. Lock-
    free Lock-free means that no resource is locked, all threads can access and modify the same resource, but only one thread can modify it successfully at the same time.
    The feature of lock-free is that the modification operation will be performed in a loop, and the thread will continue to try to modify the shared resource. If there is no conflict, the modification is successful and exits, otherwise it will continue to loop. If there are multiple threads modifying the same value, there must be one thread that can modify it successfully, while other threads that fail to modify it will continue to retry until the modification succeeds.
  2. Biased lock
    When the synchronized code block is executed for the first time, the lock object becomes a biased lock (the lock flag in the object header is modified through CAS), which literally means a lock that is "biased to the first thread that acquires it". After executing the synchronization code block, the thread will not actively release the bias lock. When the synchronization code block is reached for the second time, the thread will judge whether the thread holding the lock is itself (the thread ID holding the lock is also in the object header), and if so, it will continue to execute normally. Since the lock was not released before, there is no need to re-lock it here. If there is only one thread that uses the lock from beginning to end, it is obvious that there is almost no additional overhead for biasing the lock, and the performance is extremely high. Biased lock means that when a piece of synchronization code is always accessed by the same thread, that is, when there is no competition among multiple threads, then the thread will automatically acquire the lock during subsequent access, thereby reducing the consumption of acquiring the lock, that is Improve performance. When a thread accesses a synchronized code block and acquires a lock, it will store the lock-biased thread ID in the Mark Word. When the thread enters and exits the synchronized block, it no longer uses the CAS operation to lock and unlock, but detects whether there is a bias lock pointing to the current thread stored in the Mark Word. The acquisition and release of lightweight locks rely on multiple CAS atomic instructions, while biased locks only need to rely on one CAS atomic instruction when replacing ThreadID. Only when other threads try to compete for the biased lock, the thread holding the biased lock will release the lock, and the thread will not actively release the biased lock. Regarding the cancellation of the biased lock, it is necessary to wait for the global security point, that is, when no bytecode is being executed at a certain point in time, it will first suspend the thread that owns the biased lock, and then determine whether the lock object is locked. If the thread is not active, set the object header to the lock-free state, cancel the bias lock, and return to the lock-free (flag bit is 01) or lightweight lock (flag bit is 00) state.
  3. **Lightweight lock (spin lock)**
    insert image description hereLightweight lock means that when the lock is a biased lock, it is accessed by another thread. At this time, the biased lock will be upgraded to a lightweight lock, and other threads It will try to acquire locks in the form of spin (see the end of the article for the introduction of spin), and the thread will not be blocked, thereby improving performance.
    The acquisition of lightweight locks is mainly caused by two situations:
    ① When the biased lock function is turned off;
    ② Due to multiple threads competing for biased locks, the biased lock is upgraded to a lightweight lock.
    Once a second thread joins the lock competition, the biased lock is upgraded to a lightweight lock (spin lock). Here we need to clarify what lock competition is: If multiple threads take turns to acquire a lock, but every time they acquire the lock, it is smooth and no blocking occurs, then there is no lock competition. Lock competition occurs only when a thread tries to acquire a lock and finds that the lock is already occupied and can only wait for it to be released.
    In the lightweight lock state, the lock competition continues, and the thread that has not grabbed the lock will spin, that is, it will continuously loop to determine whether the lock can be successfully acquired. The operation of acquiring a lock is actually to modify the lock flag in the object header through CAS. First compare whether the current lock flag is "released", and if so, set it to "locked". The comparison and setting happen atomically. This is considered to have grabbed the lock, and then the thread modifies the current lock holder information to itself. Long-term spin operation is very resource-intensive. If one thread holds a lock, other threads can only consume the CPU in place and cannot perform any effective tasks. This phenomenon is called busy-waiting. If multiple threads use a lock, but no lock competition occurs, or very slight lock competition occurs, then synchronized uses lightweight locks to allow short-term busyness. This is a compromise idea, short-term busy waiting, in exchange for the overhead of switching threads between user mode and kernel mode.
  4. Heavyweight locks
    Heavyweight locks Obviously, this busy waiting has a limit (there is a counter to record the number of spins, which allows 10 cycles by default, which can be changed through virtual machine parameters). If the lock competition is serious, a thread that has reached the maximum number of spins will upgrade the lightweight lock to a heavyweight lock (CAS still modifies the lock flag, but does not modify the thread ID holding the lock). When the subsequent thread tries to acquire the lock and finds that the occupied lock is a heavyweight lock, it will directly suspend itself (instead of busy waiting) and wait for it to be woken up in the future. A heavyweight lock means that when a thread acquires a lock, all other threads waiting to acquire the lock will be blocked. In short, all control rights are given to the operating system, and the operating system is responsible for scheduling between threads and changing the state of threads. And this will frequently switch the thread running state, suspend and wake up the thread, thereby consuming a lot of system resources.

Having said that, I believe everyone should have a certain understanding of lock upgrades! ! !

Klass Word (class pointer)

This part is used to store the type pointer of the object, which points to its class metadata, and the JVM uses this pointer to determine which class the object is an instance of. The bit length of the pointer is a word size of the JVM, that is, a 32-bit JVM is 32 bits, and a 64-bit JVM is 64 bits.
If there are too many objects in the application, using 64-bit pointers will waste a lot of memory. Statistically, a 64-bit JVM will consume 50% more memory than a 32-bit JVM. In order to save memory, you can use the option +UseCompressedOops to enable pointer compression, where oop is the ordinary object pointer ordinary object pointer. When this option is turned on, the following pointers are compressed to 32 bits:

The property pointer of each Class (i.e. static variable)
the property pointer of each object (i.e. object variable)
each element pointer of the ordinary object array
Of course, not all pointers will be compressed, some special types of pointers JVM will not optimize, For example, the Class object pointer pointing to PermGen (the Class object pointer pointing to the metaspace in JDK8), local variables, stack elements, input parameters, return values, and NULL pointers, etc.

array length

If the object is an array, the object header needs additional space to store the length of the array, and the length of this part of data varies with the JVM architecture: on a 32-bit JVM, the length is 32 bits; on a 64-bit The JVM is 64-bit. If the +UseCompressedOops option is enabled for a 64-bit JVM, the length of the area will also be compressed from 64 bits to 32 bits

Object size calculation

main point

  1. In a 32-bit system, the space for storing Class pointers is 4 bytes, MarkWord is 4 bytes, and the object header is 8 bytes.
  2. Under the 64-bit system, the space for storing Class pointers is 8 bytes, MarkWord is 8 bytes, and the object header is 16 bytes.
  3. When pointer compression is enabled for 64-bit, the space for storing Class pointers is 4 bytes, MarkWord is 8 bytes, and the object header is 12 bytes. Array length 4 bytes + array object header 8 bytes (object reference 4 bytes (64-bit pointer compression is not enabled is 8 bytes) + array mark is 4 bytes (64-bit pointer compression is not enabled is 8 bytes )) + alignment 4 = 16 bytes.
  4. Static properties do not count towards the object size.

Guess you like

Origin blog.csdn.net/weixin_44021296/article/details/118244047