Detailed explanation of java objects and synchronized lock process

One, Java object components

Java objects are stored in memory and are divided into the following three parts

1), object header

2), instance data

3), align the padding byte

2. Object header

The java object header consists of the following three parts:

1)、Mark Word

2), Class Metadata Address (pointer to the class)

3), Array Length (array length, only for array objects)

There are two ways of object header in JVM (take 32-bit JVM as an example):

1.1. Ordinary objects:

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

1.2. Array object:

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|
 

2.1、Mark Word

Mark Word is mainly used to store the runtime data of the object itself, such as hashcode, gc generation age, object lock, GC and other related information. When the object is used as a synchronization lock by the synchronized keyword, a series of operations around the lock are Related to Mark Word. The length of Mark Word in 32-bit JVM is 32bit, and the length in 64-bit JVM is 64bit

2.1.1, Mark Word (32-bit)

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

2.1.2, Mark Word (64-bit)

|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|

Except for the difference in the number of bits, the meaning of each part of 32-bit and 64-bit is the same, and the meaning of each part is as follows:
lock: 2-bit lock status flag, because it is hoped to use as few binary bits as possible to represent as much as possible Information, so the lock flag is set. The value of the mark is different, and the meaning of the whole mark word is different, as shown below:

biased_lock lock status
0 01 no lock
1 01 Bias lock
0 00 Lightweight lock
0 10 Heavyweight lock
0 11 GC mark

biased_lock : Whether the object enables the biased lock flag, the biased lock is enabled by default, and only occupies 1 binary bit. When it is 1, it means that the object has a biased lock; when it is 0, it means that the object has no biased lock.
age : 4-digit Java object age. In the GC, if the object is copied once in the Survivor area, the age increases by 1. When the subject reaches the set threshold, it will be promoted to the old age. 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 -XX:MaxTenuringThreshold maximum value of the option is 15.
identity_hashcode : 25-bit object identification hash code, using lazy loading technology. Call method System.identityHashCode() calculation, and write the result to the object header. When the object is locked, the value will be moved to the monitor monitor
thread : the thread ID holding the bias lock
epoch : bias timestamp
ptr_to_lock_record : pointer to the lock record in the stack
ptr_to_heavyweight_monitor : pointer to the monitor monitor

Below we focus on the implementation process of synchronized synchronization keyword lock. The following takes Mark Word (32-bit) as an example. Mark Word stores different contents under different lock states, as shown below:

Lock state

25bit

4bit

1bit

2bit

23bit

2bit

Is it biased towards lock

Lock flag

no lock

HashCode of the object

Generational age

0

01

Bias lock

Thread ID

Epoch

Generational age

1

01

Lightweight lock

Pointer to the lock record in the stack

00

Heavyweight lock

Pointer to heavyweight lock

10

GC mark

air

11

Versions after JDK1.6 have added the concept of lock upgrade when dealing with synchronous locks. The JVM's processing of synchronous locks starts from biased locks. As the competition becomes more and more fierce, the processing method is upgraded from biased locks to lightweight locks. , And finally upgraded to a heavyweight lock

synchronized process

The JVM synchronized lock uses the following process to modify the Mark Word. The entire lock process can be summarized as follows:

2.1.3, the bias lock is turned on by default

1) When the object is not locked, that is, a normal object, the bias lock state is 1, the lock flag is 01, and the thread ID is 0, as shown below:

2), when the object is locked and a thread A grabs the lock, the bias lock status bit and lock flag bit are unchanged, but the thread id of the first 23bit record grabbing the lock is recorded in the thread ID, and the object enters the bias The lock status is as follows:

When there is no other thread competing for the lock of the object, after thread A releases the lock, the object will continue to maintain the biased lock state, that is, the state of step 2, and the thread ID still maintains the ID of thread A

3) When thread A acquires the lock again, the JVM finds that the object is in a biased lock state, that is, the lock flag is 01, the bias lock flag is also 1, and it is the same thread, you only need to add 1 to the number of lock acquisitions Yes, no need to reacquire the lock, directly execute the code logic

4) When thread B acquires the lock, the JVM finds that the object is in a biased lock state, and the thread ID record is not thread B, then thread B will acquire the lock through CAS spin. If B acquires the lock successfully, the object is in a biased lock state , Modify the thread ID to the ID of thread B, otherwise go to step 5

5). Since B has not acquired the lock, it indicates that the lock competition is fierce. At this time, the bias lock will be upgraded to a lightweight lock. The JVM will open up a separate space in the thread stack of the current thread, which saves the pointer to the object lock Mark Word, and at the same time saves the pointer to this space in the object lock Mark Word. The above two save operations are CAS operations. If the save is successful, it means that the lock is successfully acquired, and the lock flag is changed to 00. At this time, the object enters the lightweight lock state, as shown below:

6). If B fails to acquire the lock, it means that the lock has failed to upgrade to a lightweight lock. At this time, the lock will expand to a heavyweight lock, and the lock flag will be changed to 10. After that, all threads that have not acquired the lock will be in a blocked state. , Does not spin to acquire the lock. The JVM will create a separate ObjectMonitor object monitor, the Mark Word pointer points to the monitor, and all blocked threads will join the linked list and queue to obtain the ObjectMonitor object lock (ie monitorenter, monitorexit logic), as shown below:

Once the object lock is expanded to a heavyweight lock, even after all threads release the lock, the object lock will not be restored to a lock-free or biased lock state. After that, all thread locking operations are heavyweight locks.

2.1.4. Disable biased locking ( -XX:-UseBiasedLocking )

1) When the object is not locked, that is, a normal object, the biased lock state is 0, the lock flag is 01, and the hashCode value of the object is as follows:

2) When the object is locked and a thread A grabs the lock, the JVM will open up a separate space in the thread stack of the current thread, which saves the pointer to the object lock Mark Word, and at the same time in the object lock Mark Word Save the pointer to this space. The above two save operations are CAS operations. If the save is successful, it means that the lock is acquired successfully, and the lock flag bit is changed to 00. At this time, the object enters the lightweight lock state.

When there is no other thread competing for the lock of the object, after thread A releases the lock, the object will return to the lock-free state, which is the state of step 1.

3) When thread A acquires the lock again, the JVM finds that if the object is in a lock-free state, follow step 2 to execute, otherwise (reentry lock) only need to add 1 to the number of times the lock is acquired, without re-acquiring the lock , Execute code logic directly

4) When thread B acquires the lock, the JVM finds that the object is in a lightweight lock state. The JVM will open up a separate space in the thread stack of the current thread, which saves the pointer to the object lock Mark Word, and at the same time locks the object The pointer to this space is saved in the Mark Word. The above two save operations are CAS operations. If the save is successful, it means that the lock is successfully acquired. The lock flag is changed to 00. At this time, the object enters the lightweight lock state, as shown below Show:

5) If B fails to acquire the lock, the lock will expand to a heavyweight lock, and the lock flag will be changed to 10. After that, all threads that have not acquired the lock will be in a blocked state. The JVM will create a separate ObjectMonitor object monitor, the Mark Word pointer points to the monitor, and all blocked threads will join the linked list and queue to obtain the ObjectMonitor object lock (ie monitorenter, monitorexit logic), as shown below:

Once the object lock is expanded to a heavyweight lock, even after all threads release the lock, the object lock will not be restored to a lock-free or biased lock state. After that, all thread locking operations are heavyweight locks.

2.1.5 java output object information

1), maven introduces the jol-core jar package, as shown below:

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.13</version>
        </dependency>

2), sample demo:

/**
 * synchronized 测试
 *
 * -XX:-UseBiasedLocking 禁止偏向锁,默认偏向锁是开启的
 *
 * @author supu
 * @since 2020-08-31 11:00
 **/
public class SynchronizedDemo {

    public static void main(String[] args) {
        System.out.println(VM.current().details());

        A a = new A();
        a.setAge(10);
        a.setName("zs");

        ClassLayout c2 = ClassLayout.parseInstance(a);

        System.out.println(c2.toPrintable());

        new Thread(() -> {
            synchronized (a){
                System.out.println("*********** a 第一次被锁定后的对象头 markword ***********");
                System.out.println(c2.toPrintable());


                /*synchronized (a){
                    System.out.println("*********** a 第二次被锁定后的对象头 markword ***********");
                    System.out.println(c2.toPrintable());
                }*/

                try {
                    TimeUnit.MILLISECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        try {
            TimeUnit.MILLISECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 当多个线程竞争锁时,偏向锁升级为轻量级锁还是重量级锁的条件时,后者获取锁的线程是否能在指定时间内自旋CAS获取到锁,
        // 如果获取到锁了则升级为轻量级锁,否则升级为重量级锁

        synchronized (a){
            System.out.println("*********** a 第二次被锁定后的对象头 markword ***********");
            System.out.println(c2.toPrintable());
        }

        System.out.println("************ a 释放锁定后的对象头 markword **********");
        System.out.println(c2.toPrintable());

    }

    static class A {
        private int age;
        private String name;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            A a = (A) o;
            return age == a.age &&
                    Objects.equals(name, a.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(age, name);
        }
    }
}

2.2, Class Metadata Address (pointer to the class)

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

1), the static variable attribute pointer of each Class (ie static variable)

2), the member variable attribute pointer of each object (ie member variable)

3), each element pointer of the ordinary object array

Of course, not all pointers are compressed. Some special types of pointers will not be optimized by JVM, such as PermGen's Class object pointer (the Class object pointer to metaspace in JDK8), local variables, stack elements, input parameters, and return values. And NULL pointers, etc.

2.3. Array Length (array length, only for array objects)

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

Three, instance data

The instance data of the object is the attributes and their values ​​that can be seen in the java code

Fourth, align the padding byte

Because the JVM requires that the memory size of the java object should be a multiple of 8bit, there are a few bytes behind to fill in the size of the object to a multiple of 8bit, and there is no special function other than that.

Five, reference

https://wiki.openjdk.java.net/display/HotSpot/Synchronization

https://www.jianshu.com/p/3d38cba67f8b

Guess you like

Origin blog.csdn.net/ywlmsm1224811/article/details/108322059