springboot e-commerce project mall4j( https://gitee.com/gz-yami/mall4j)
Verify the whole process of object header change during synchronized lock upgrade
jdk version: 1.8
System: Windows 10 64 bit
jvm startup parameters: - XX:BiasedLockingStartupDelay=0 (cancel delayed load bias lock)
First, we need to know several concepts
- Memory structure of java non array objects (ordinary objects)
If it is an array object, it will occupy another length space (4 bytes) to record the length of the array.
- markword format of java object (on 64 bit virtual machine)
3.synchronized four lock upgrade process and when to upgrade (not detailed)
Check whether the lock flag bit changes correspondingly in these four lock states through the actual coding
Introducing jol Toolkit
<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>
Unlocked state
@Test public void test01() throws Exception { Object o = new Object(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); }
results of enforcement
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000101 00000000 00000000 00000000) (5) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
The object header in the first and second lines is the memory of markword, the object header in the third line is the content of class pointer, and the fourth line is alignment filling.
Just focus on the first and second lines.
Because the first and second lines are printed from low to high, they need to be viewed in reverse to correspond one by one with the lock status table above.
00000000 00000000 00000000.
In comparison with the last three digits of the table, focus on whether the bias lock is 1 and the lock flag is 01. Theoretically, the bias lock flag should be 0, which is caused by adding a startup parameter to cancel the delayed loading bias lock. If the startup parameter is removed, the bias lock flag is 0.
When the JVM starts, it will carry out a series of complex activities, such as loading configuration, system class initialization and so on. In this process, a large number of synchronized keywords will be used to lock objects, and most of these locks are not biased locks. In order to reduce the initialization time, the JVM delays loading the bias lock by default, and we disable it, so the bias lock flag bit becomes 1.
Even so, it can still be seen that the underlined part is the id storage location of the biased thread. At present, it is all 0, that is, the object is not biased to any thread, so the current object is still biased
That is, the current lock state of the object is unlocked.
Biased locked state
@Test public void test2() throws Exception { Object o = new Object(); synchronized (o) {} System.out.println(ClassLayout.parseInstance(o).toPrintable()); }
results of enforcement
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 05 e8 d1 02 (00000101 11101000 11010001 00000010) (47310853) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Similarly, first arrange the first two rows in the order in the table, as follows:
00000000 00000000 00000000 00000000 00000010 11010001 11101000 00000101
This time, we can see that the bias lock flag bit is 1, the lock flag bit is 01, and the thread id of the bias lock is also occupied, so it is obvious that the object thread is in the bias lock state.
At the same time, it can be seen from the code that O is printed only after o is synchronized and the lock is released. It can be concluded that the biased lock state will not be revoked actively, but will continue to retain its state.
Lightweight Locking
@Test public void test3() throws Exception { Object o = new Object(); synchronized (o) {} new Thread(() -> { synchronized (o) { System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }).start(); TimeUnit.SECONDS.sleep(10); }
results of enforcement
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 00 ef a5 1e (00000000 11101111 10100101 00011110) (514191104) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
00000000 00000000 00000000 00000000 00011110 10100101 11101111 00000000
According to the comparison table, it is obvious that the O is in the lightweight lock state at this time. Because the main thread locks o first, O is in the bias lock, and then another thread locks O. before locking, the bias lock will expand into a lightweight lock.
To sum up, it is proved.
Heavyweight lock
@Test public void test4() throws Exception { Object o = new Object(); AtomicInteger index = new AtomicInteger(0); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (o) { index.getAndIncrement(); if(index.get() == 9) System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }).start(); } TimeUnit.SECONDS.sleep(10); }
results of enforcement
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) fa 05 d7 1c (11111010 00000101 11010111 00011100) (483853818) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
00000000 00000000 00000000 00000000 00011100 11010111 00000101 11111010
There is a lot of lock competition. Theoretically, o should be in the heavyweight lock state. It is obvious from the comparison table that o is in the heavyweight lock state, which is proved.
Let's look at the following two situations
-
Calculate the hashcode of o before locking it. What state is o in when locking it
@Test public void test5() throws Exception { Object o = new Object(); o.hashCode(); synchronized (o) { System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }
Theoretically, it should still be biased towards locking, so look at the execution results.
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) b8 e4 ac 02 (10111000 11100100 10101100 00000010) (44885176) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
At this time, the lock flag bit is 00, which is a lightweight lock, not a biased lock.
Conclusion: after locking, the hashcode calculated object will directly expand into a lightweight lock and skip the bias lock.
-
After locking o, calculate the hashcode of O. what is the status of o at this time
@Test public void test6() throws Exception { Object o = new Object(); synchronized (o) { o.hashCode(); System.out.println(ClassLayout.parseInstance(o).toPrintable()); } }
In theory, o here should be biased lock. See the execution results:
java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) ba c7 52 1c (10111010 11000111 01010010 00011100) (475187130) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
At this time, the lock flag bit is 10, which is a heavyweight lock, not a bias lock.
Conclusion: as long as the hashcode is calculated, the object in the biased locking state will directly expand into a heavyweight lock.
expand:
Verify that the hashcode calculated in the code is consistent with the value in the corresponding record hashcode in markword
@Test public void test7() throws Exception { Object o = new Object(); System.out.println(o.hashCode()); System.out.println(ClassLayout.parseInstance(o).toPrintable()); }
Output result:
1174290147 java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 e3 3e fe (00000001 11100011 00111110 11111110) (-29433087) 4 4 (object header) 45 00 00 00 (01000101 00000000 00000000 00000000) (69) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 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 markword, i.e. 00000000 00000000 01000101 11111110 00111110 11100011 00000001
Referring to the table at the beginning of the article, it can be seen that from the 26th bit to the 56th bit are the bits for recording hashcode. I.e. 1000101 11111111110 00111110 11100011, convert it to decimal, and the result is 1174290147. It is consistent with the result output in the code.
springboot e-commerce project mall4j( https://gitee.com/gz-yami/mall4j)