Principle of java locks

Posted by hungryOrb on Tue, 04 Jan 2022 16:42:55 +0100

concept

1. java objects

  • Object header (Object header) 12byte. Object header is composed of Mark word (8byte) and Klass pointer (turn on pointer compression size is 4 byte, pointer to object's metadata, pointer to template of method area class class class).

  • Aligned bytes (consisting of instance data of the object and byte alignment. Because objects on 64-bit virtual machines must be a multiple of 8) by 4 bytes.

Object header(128 bits)
Mark word(64 bits)Klass pointer(64 bits without pointer compression turned on)
unused: 25 — identity_hashcode: 31 — unused: 1 — age: 4 — biased_lock: 1 — lock: 2OOP to meadata obj (001: unlocked)
thread: 54 — epoch: 2 — unused: 1 — age: 4 — biased_lock: 1 — lock: 2OOP to meadata obj (101: biased lock)
ptr_to_lock_record: 62 — lock: 2OOP to meadata obj (000: lightweight lock)
ptr_to_heavyweight_monitor: 62 — lock: 2OOP to meadata obj (010: weight lock)
— lock: 21001:OOP to meadata obj (status of GC tag)

Conclusion 1: It can be seen from the object header identification above that the last three values represent the lock state of the object, only the first value of the lock is preferred. When the object is hashcoded, the value of hashcode occupies part of the position in the object header Mark word(64 bits). The first three values will be occupied by the value of hashcode. So - an object does not have a skewed lock state after hashcode operation, which is directly skewed lock. (as will be demonstrated below)

Get ready

Add JOL to analyze java's object layout

<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol‐core</artifactId>
<version>0.9</version>
</dependency>

test

Print results:

Explain:

  • Windows10 is a small-end operating system. This means that Mark word(64 bits) will be arranged upside down. The last three 001 of 00000001 of the first eight bits in VALUE represent unlocked state. Corresponds to the code of object a of the author's experiment (object A is an empty object without any attributes, no separate screenshots)
  • Mark word(64 bits here 4byte + 4byte) and Klass pointer(32 bits here 4byte turns on pointer compression) plus four unknown bytes make up 16 bytes of 8. They are marked in the two boxes on the left of the picture. It is worth noting that Klass pointer is a pointer to object-oriented metadata. There are still 4 bytes left to translate loss due to the next object alignment, which means that the next object alignment causes the loss. It means that the remaining 4 bytes contain the instance data of the object, but the author's A is an empty object, and the size of the object on the 64-bit virtual machine must be a multiple of 8. Object's instance data is 0B (here you can test this conclusion by adding attributes to Object A), so it's complemented for the convenience of jvm processing.

Start Analysis

Analysis One

Run the following code

    public void test2() throws NoSuchFieldException, IllegalAccessException {
        Thread.sleep(5000);//jvm does not turn on deflection lock by default for the first four seconds
        out.println("befor hash");
        //No object header before HASHCODE was calculated
        out.println(ClassLayout.parseInstance(a).toPrintable());
        //hashcode of JVM calculation
        out.println("jvm‐‐‐‐‐‐‐‐‐‐‐‐0x" + Integer.toHexString(a.hashCode()));
        HashUtil.countHash(a);
        //When hashcode is calculated, we can see the change of information in the object header
        out.println("after hash");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a) {
            out.println("object a Locked ---------");
            out.println(ClassLayout.parseInstance(a).toPrintable());

        }
    }

  • You can see the first line of the VALUE, and the last 34 bits, after hashcode operations, change from zero to valuable.
  • The object header printed after synchronized (a) locking the object is directly 1000 lightweight locks, with no bias lock 101 (about removing hashcode operations, which occurs below the process of printing out the bias lock)

Analysis 2


The test code is as follows

 public void test3() throws NoSuchFieldException, IllegalAccessException {
        A a = new A();
        out.println("befor lock");
        //No object header before lock
        out.println(ClassLayout.parseInstance(a).toPrintable());
        synchronized (a) {
            out.println("object a Locked ---------");
            out.println(ClassLayout.parseInstance(a).toPrintable());

        }
        //When the lock is calculated, we can see the information changes in the object header
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }
  • You can see that the object heads of a before, during and after direct printing are as follows: unlocked 001, medium: lightweight lock 000, and back: unlocked 001

  • Resource competition exists for most objects during the internal initialization startup of the jvm, which means that most of these objects will have the process of upgrading from a lock-free to a lightweight lock, then eliminating the bias lock to a lightweight lock, which is a relatively consuming performance upgrade process.

  • Therefore, within the first 4 seconds after the program starts, the jvm turns off the bias lock by default, that is, within the first 4 seconds, all upgrades to the bias lock are directly upgraded to the lightweight lock.

Analysis Three

  • You can see that during the 5S sleep period when the program starts, the main thread waits for jvm to load and print with bias lock enabled, and 101 is found before locking, which means that the A object becomes deflectable at this time, but the 24bits after the first line of VALUE are null, which means that the A object becomes deflectable after 5s, but does not deflect any threads.
  • After locking, the result is 101, and the skewed thread is worth it.
  • After exiting the synchronization block, the bias state of 101 has not been eliminated. This reason will be discussed next. The skew thread does not change, and an object can generally only skew to one thread id. Special case: After being locked by another thread more than a certain number of times, the 24 bits that will re-enter the back of the lock will change. Next.

Analysis Four

    public void test6() throws InterruptedException {
        Thread.sleep(5000);
        out.println("befre lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
        sync();
        out.println("after lock");
        out.println(ClassLayout.parseInstance(a).toPrintable());
    }


    public static void sync()  {
        synchronized (a) {
            out.println("lock ing");
            out.println(ClassLayout.parseInstance(a).toPrintable());
        }

    }

  • You can see a simple print view of the lightweight lock's object header, which changes from 001 to 000 to 001, with the thread id appearing and clearing.

Analysis Five

    public void test5() {
/**
 * ‐XX:BiasedLockingStartupDelay=20000 ‐XX:BiasedLockingStartupDelay=0
 * Adding the above parameters to indicate that unlocking a bias lock is not represented by a lightweight lock and the resulting timing compares the performance of a bias lock with that of a lightweight lock
 */
        A a = new A();
        long start = System.currentTimeMillis();
//Call the synchronization method 1000000000L to calculate 1000000000L++, compare the performance of bias and lightweight locks
//If nothing happens, the results are often obvious
        for (int i = 0; i < 1000000000L; i++) {
            a.parse();
        }
        long end = System.currentTimeMillis();
        System.out.println(String.format("%sms", end - start));
    }

Startup parameters are not added, that is, lightweight locks calculate the cumulative time of 1000000000L as follows

Turning on the deflection lock takes the following time:

  • Lightweight lock 8475ms vs. bias lock 1646ms
  • The performance of locks in the upgrade process is also very different.
  • Multiple threads are competing for a lock at the same time, resulting in a weight lock whose performance is time consuming for the reader to test.

Topics: Java Back-end Concurrent Programming