The expansion process of synchronized lock (lock upgrade process) in java Concurrent note 4
In this paper, we discuss the expansion process of bias lock (batch bias, batch revocation), lightweight lock, heavyweight lock and lock (that is, the upgrading process of lock) through a large number of example codes and hotspot source code analysis.
Let's talk about why we need locks first.
Because concurrent situation is a concept of correctness in a multi-threaded environment to ensure thread security, that is, to ensure the correctness of shared and modifiable state in a multi-threaded environment (where the state refers to the data in the program), we can use synchronized keyword to program in java program. Lock.
When a synchronized block of code is declared, the compiled bytecode will contain the monitor enter instruction and the monitor exit instruction. Both instructions consume an element of the reference type on the operand stack (that is, the reference in the synchronized keyword brackets) as the lock object to be unlocked.
(Note: Before jdk 1.6, the synchronized keyword only represented heavyweight locks. After 1.6, it was divided into biased locks, lightweight locks and heavyweight locks.)
The so-called lock upgrade and downgrade is the mechanism of JVM optimizing synchronized operation. When JVM detects different competition conditions, it will automatically switch to the appropriate lock implementation. This kind of switch is lock upgrade and downgrade.
When no competition occurs, biased locks are used by default. JVM uses CAS operations (compare and swap) to set the thread ID in the Mark Word section of the object's head to indicate that the object is biased towards the current thread, so it does not involve real mutexes. The assumption is that in many application scenarios, most objects will be locked by at most one thread in their lifetime, and the use of biased locks can reduce non-competitive overhead.
If another thread tries to lock an object that has been biased, the JVM needs to revoke the biased lock and switch to a lightweight lock implementation. Lightweight locks rely on CAS operation Mark Word to attempt to acquire locks, and if the retry succeeds, use lightweight locks; otherwise, further upgrade to heavy locks
So let's look at a synchronized code analysis:
java code:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestDemo {
}
public class DemoExample1 {
static TestDemo testDemo; public static void main(String[] args) throws Exception { testDemo= new TestDemo(); synchronized (testDemo){ System.out.println("lock ing"); testDemo.hashCode(); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } }
}
Run and analyze the TestDemo.class file command:
1
javap -c DemoExample1.class
The results are as follows:
Compiled from "DemoExample1.java"
public class com.boke.DemoExample1 {
static com.boke.TestDemo testDemo;
public com.boke.DemoExample1();
Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code: 0: new #2 // class com/boke/TestDemo 3: dup 4: invokespecial #3 // Method com/boke/TestDemo."<init>":()V 7: putstatic #4 // Field testDemo:Lcom/boke/TestDemo; 10: getstatic #4 // Field testDemo:Lcom/boke/TestDemo; 13: dup 14: astore_1 15: monitorenter 16: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 19: ldc #6 // String lock ing 21: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 24: getstatic #4 // Field testDemo:Lcom/boke/TestDemo; 27: invokevirtual #8 // Method java/lang/Object.hashCode:()I 30: pop 31: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 34: getstatic #4 // Field testDemo:Lcom/boke/TestDemo; 37: invokestatic #9 // Method org/openjdk/jol/info/ClassLayout.parseInstance:(Ljava/lang/Object;)Lorg/openjdk/jol/info/ClassLayout; 40: invokevirtual #10 // Method org/openjdk/jol/info/ClassLayout.toPrintable:()Ljava/lang/String; 43: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 46: aload_1 47: monitorexit 48: goto 56 51: astore_2 52: aload_1 53: monitorexit 54: aload_2 55: athrow 56: return Exception table: from to target type 16 48 51 any 51 54 51 any
}
From the bytecode, you can see that there is one monitor enter instruction and several monitor exit instructions. This is because the jvm needs to ensure that the acquired locks are unlocked on both the normal execution path and the exception execution path.
We can abstractly understand that each lock object has a lock counter and a pointer to the thread holding the lock:
When monitorenter is executed, if the counter of the target lock object is 0, then it is not held by other threads. In this case, the Java virtual opportunity sets the holding thread of the lock object to the current thread and adds its counter to 1.
If the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, then the Java virtual machine can add its counter to 1, otherwise it needs to wait until the holding thread releases the lock. When monitoring exit is executed, the Java virtual machine needs to subtract the counter of the lock object by 1. When the counter is reduced to zero, it means that the lock has been released.
This counter is used to allow the same thread to acquire the same lock repeatedly. For example, if there are multiple synchronized methods in a Java class, the calls between these methods, whether direct or indirect, involve repeated locking operations on the same lock. Therefore, we need to design such a reentrant feature to avoid implicit constraints in programming.
Let's look at a case: in the case of unlocking, we simulate the operation of two shared states by taking two values and comparing them.
java code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class DemoExample3 {
public int sharedState; public void nonSafeAction() { while (sharedState < 100000) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.println("Observed data race, former is " + former + ", " + "latter is " + latter); } } } public static void main(String[] args) throws InterruptedException { final DemoExample3 demoExample3 = new DemoExample3(); Thread thread1 = new Thread() { @Override public void run() { demoExample3.nonSafeAction(); } }; Thread thread2 = new Thread() { @Override public void run() { demoExample3.nonSafeAction(); } }; thread1.start(); thread2.start(); thread1.join(); thread2.join(); }
}
Printed results without synchronized keywords (intercept part):
Observed data race, former is 55179, latter is 55181
Observed data race, former is 56752, latter is 56754
Observed data race, former is 58304, latter is 58306
Observed data race, former is 60340, latter is 60342
Observed data race, former is 61627, latter is 61629
Observed data race, former is 63107, latter is 62946
Observed data race, former is 64029, latter is 64029
Observed data race, former is 65579, latter is 65581
Observed data race, former is 67315, latter is 67317
Observed data race, former is 68542, latter is 68542
Observed data race, former is 70687, latter is 70687
Observed data race, former is 72654, latter is 72656
Observed data race, former is 74644, latter is 74646
It will be found that printing out a lot of values that match the if (former!= latter - 1) condition is wrong, and the correct result should be none.
Let's look at the code with the synchronized keyword:
java code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class DemoExample3 {
public int sharedState; public void nonSafeAction() { while (sharedState < 100000) { synchronized (this) { int former = sharedState++; int latter = sharedState; if (former != latter - 1) { System.out.println("Observed data race, former is " + former + ", " + "latter is " + latter); } } } } public static void main(String[] args) throws InterruptedException { final DemoExample3 demoExample3 = new DemoExample3(); Thread thread1 = new Thread() { @Override public void run() { demoExample3.nonSafeAction(); } }; Thread thread2 = new Thread() { @Override public void run() { demoExample3.nonSafeAction(); } }; thread1.start(); thread2.start(); thread1.join(); thread2.join(); }
}
This time look at the printed results with synchronized keywords:
Process finished with exit code 0
It shows that we can avoid other threads modifying sharedState concurrently by synchronizing the two assignment processes and using this as the mutually exclusive unit. This is also the concurrent situation I just mentioned, in order to ensure the thread's security, we can guarantee it by locking.
After explaining why we need locks, we will introduce the expansion process of biased locks, lightweight locks, heavyweight locks and locks.
Firstly, we analyze the expansion process of lock (lock upgrade process) from jvm source code.
In jvm, synchronized behavior is part of jvm runntime, so we need to find the implementation of Runtime-related functions first. By querying code like "monitor_enter" or "Monitor Enter", it is intuitive to locate:
sharedRuntime.cpp(http://hg.openjdk.java.net/jdk/jdk/file/6659a8f57d78/src/hotspot/share/runtime/sharedRuntime.cpp It is the base class for interpreter and compiler runtime:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Handles the uncommon case in locking, i.e., contention or an inflated lock.
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc _obj, BasicLock lock, JavaThread* thread))
// Disable ObjectSynchronizer::quick_enter() in default config
// on AARCH64 and ARM until JDK-8153107 is resolved.
if (ARM_ONLY((SyncFlags & 256) != 0 &&)
AARCH64_ONLY((SyncFlags & 256) != 0 &&) !SafepointSynchronize::is_synchronizing()) { // Only try quick_enter() if we're not trying to reach a safepoint // so that the calling thread reaches the safepoint more quickly. if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
}
// NO_ASYNC required because an async exception on the state transition destructor
// would leave you with the lock held and it would never be released.
// The normal monitorenter NullPointerException is thrown without acquiring a lock
// and the model is that an exception implies the method failed.
JRT_BLOCK_NO_ASYNC
oop obj(_obj);
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(THREAD, obj);
// When the JVM starts, we can specify whether to open the bias lock or not.
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
// Fas_enter is our familiar complete lock acquisition path
ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
} else {
// Slo_enter bypasses biased locks and goes directly into lightweight lock acquisition logic
ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
}
assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
JRT_BLOCK_END
JRT_END
synchronizer.cpp(https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp The various foundations of JVM synchronization (not only synchronized logic, but also triggered Monitor actions from native code, JNI, can be found in (jni_enter/jni_exit):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// -----------------------------------------------------------------------------
// Fast Monitor Enter/Exit
// This the fast monitor enter. The interpreter and compiler use
// some assembly copies of this code. Make sure update those code
// if the following function is changed. The implementation is
// extremely sensitive to race condition. Be careful.
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) { //biasedLocking defines biased lock-related operations, and revoke_and_rebias revokeat safe point defines processing when a security point is detected BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return; } } else { assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
// If acquisition of biased locks fails, enter slow_enter and lock escalation
slow_enter(obj, lock, THREAD);
}
// -----------------------------------------------------------------------------
// Interpreter/Compiler Slow Case
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have been
// failed in the interpreter/compiler code.
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must // be visible <= the ST performed by the CAS. // Copy the current Mark Word to Displaced Header lock->set_displaced_header(mark); // Setting Mark Wo of Object with CAS if (mark == obj()->cas_set_mark((markOop) lock, mark)) { return; } // Fall through to inflate() ... // Check for competition
} else if (mark->has_locker() &&
THREAD->is_lock_owned((address)mark->locker())) { assert(lock != mark->locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock"); // Eliminate lock->set_displaced_header(NULL); return;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
// Reset Displaced Header
lock->set_displaced_header(markOopDesc::unused_mark());
// lock expansion
ObjectSynchronizer::inflate(THREAD,
obj(), inflate_cause_monitor_enter)->enter(THREAD);
}
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have
// failed in the interpreter/compiler code. Simply use the heavy
// weight monitor should be ok, unless someone find otherwise.
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit(object, lock, THREAD);
}
// lock expansion
ObjectMonitor ATTR ObjectSynchronizer::inflate (Thread Self, oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
for (;;){// spin
const markOop mark = object->mark() ; assert (!mark->has_bias_pattern(), "invariant") ; // The mark can be in one of the following states: // * Inflated - just return // * Stack-locked - coerce it to inflated // * INFLATING - busy wait for conversion to complete // * Neutral - aggressively inflate the object. // * BIASED - Illegal. We should never see this // CASE: inflated, heavyweight lock if (mark->has_monitor()) {//Judging whether the current lock is a heavyweight lock ObjectMonitor * inf = mark->monitor() ;//Get a pointer to ObjectMonitor assert (inf->header()->is_neutral(), "invariant"); assert (inf->object() == object, "invariant") ; assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid"); return inf ; } // CASE: inflation in progress - inflating over a stack-lock. Expansion waiting (other threads are moving from lightweight to expansive locking) // Some other thread is converting from stack-locked to inflated. // Only that thread can complete inflation -- other threads must wait. // The INFLATING value is transient. // Currently, we spin/yield/park and poll the markword, waiting for inflation to finish. // We could always eliminate polling by parking the thread on some auxiliary list. if (mark == markOopDesc::INFLATING()) { TEVENT (Inflate: spin while INFLATING) ; ReadStableMark(object) ; continue ; } // CASE: stack-lock stack lock (lightweight lock) // Could be stack-locked either by this thread or by some other thread. // // Note that we allocate the objectmonitor speculatively, _before_ attempting // to install INFLATING into the mark word. We originally installed INFLATING, // allocated the objectmonitor, and then finally STed the address of the // objectmonitor into the mark. This was correct, but artificially lengthened // the interval in which INFLATED appeared in the mark, thus increasing // the odds of inflation contention. // // We now use per-thread private objectmonitor free lists. // These list are reprovisioned from the global free list outside the // critical INFLATING...ST interval. A thread can transfer // multiple objectmonitors en-mass from the global free list to its local free list. // This reduces coherency traffic and lock contention on the global free list. // Using such local free lists, it doesn't matter if the omAlloc() call appears // before or after the CAS(INFLATING) operation. // See the comments in omAlloc(). if (mark->has_locker()) { ObjectMonitor * m = omAlloc (Self) ;//Get an available ObjectMonitor // Optimistically prepare the objectmonitor - anticipate successful CAS // We do this before the CAS in order to minimize the length of time // in which INFLATING appears in the mark. m->Recycle(); m->_Responsible = NULL ; m->OwnerIsThread = 0 ; m->_recursions = 0 ; m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; if (cmp != mark) {//CAS failure //CAS failure, indicating conflict, spin waiting //CAS failure, indicating conflict, spin waiting //CAS failure, indicating conflict, spin waiting //CAS failure. omRelease (Self, m, true) ;//Release monitor lock continue ; // Interference -- just retry } // We've successfully installed INFLATING (0) into the mark-word. // This is the only case where 0 will appear in a mark-work. // Only the singular thread that successfully swings the mark-word // to 0 can perform (or more precisely, complete) inflation. // // Why do we CAS a 0 into the mark-word instead of just CASing the // mark-word from the stack-locked value directly to the new inflated state? // Consider what happens when a thread unlocks a stack-locked object. // It attempts to use CAS to swing the displaced header value from the // on-stack basiclock back into the object header. Recall also that the // header value (hashcode, etc) can reside in (a) the object header, or // (b) a displaced header associated with the stack-lock, or (c) a displaced // header in an objectMonitor. The inflate() routine must copy the header // value from the basiclock on the owner's stack to the objectMonitor, all // the while preserving the hashCode stability invariants. If the owner // decides to release the lock while the value is 0, the unlock will fail // and control will eventually pass from slow_exit() to inflate. The owner // will then spin, waiting for the 0 value to disappear. Put another way, // the 0 causes the owner to stall if the owner happens to try to // drop the lock (restoring the header from the basiclock to the object) // while inflation is in-progress. This protocol avoids races that might // would otherwise permit hashCode values to change or "flicker" for an object. // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable. // 0 serves as a "BUSY" inflate-in-progress indicator // fetch the displaced mark from the owner's stack. // The owner can't die or unwind past the lock while our INFLATING // object is in the mark. Furthermore the owner can't complete // an unlock on the object, either. markOop dmw = mark->displaced_mark_helper() ; assert (dmw->is_neutral(), "invariant") ; //CAS succeeded, setting ObjectMonitor's _header, _owner, and _object, etc. // Setup monitor fields to proper values -- prepare the monitor m->set_header(dmw) ; // Optimization: if the mark->locker stack address is associated // with this thread we could simply set m->_owner = Self and // m->OwnerIsThread = 1. Note that a thread can inflate an object // that it has stack-locked -- as might happen in wait() -- directly // with CAS. That is, we can avoid the xchg-NULL .... ST idiom. m->set_owner(mark->locker()); m->set_object(object); // TODO-FIXME: assert BasicLock->dhw != 0. // Must preserve store ordering. The monitor state must // be stable at the time of publishing the monitor address. guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ; object->release_set_mark(markOopDesc::encode(m)); // Hopefully the performance counters are allocated on distinct cache lines // to avoid false sharing on MP systems ... if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ; TEVENT(Inflate: overwrite stacklock) ; if (TraceMonitorInflation) { if (object->is_instance()) { ResourceMark rm; tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s", (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); } } return m ; } // CASE: neutral unlocked // TODO-FIXME: for entry we currently inflate and then try to CAS _owner. // If we know we're inflating for entry it's better to inflate by swinging a // pre-locked objectMonitor pointer into the object header. A successful // CAS inflates the object *and* confers ownership to the inflating thread. // In the current implementation we use a 2-step mechanism where we CAS() // to inflate and then CAS() again to try to swing _owner from NULL to Self. // An inflateTry() method that we could call from fast_enter() and slow_enter() // would be useful. assert (mark->is_neutral(), "invariant"); ObjectMonitor * m = omAlloc (Self) ; // prepare m for installation - set monitor to initial state m->Recycle(); m->set_header(mark); m->set_owner(NULL); m->set_object(object); m->OwnerIsThread = 1 ; m->_recursions = 0 ; m->_Responsible = NULL ; m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) { m->set_object (NULL) ; m->set_owner (NULL) ; m->OwnerIsThread = 0 ; m->Recycle() ; omRelease (Self, m, true) ; m = NULL ; continue ; // interference - the markword changed - just retry. // The state-transitions are one-way, so there's no chance of // live-lock -- "Inflated" is an absorbing state. } // Hopefully the performance counters are allocated on distinct // cache lines to avoid false sharing on MP systems ... if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ; TEVENT(Inflate: overwrite neutral) ; if (TraceMonitorInflation) { if (object->is_instance()) { ResourceMark rm; tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s", (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); } } return m ;
}
}
The realization of expansion process is relatively complex, and the process of realization is as follows:
1. The whole expansion process is completed under spin.
2. Mark - > has_monitor() method judges whether the current lock is a heavy level lock, that is, Mark Word's lock identifier bit is 10. If the current state is a heavy level lock, execute step (3), otherwise execute step (4);
3. The mark - > monitor () method obtains the pointer to ObjectMonitor and returns to show that the expansion process has been completed.
4. If the current lock is expanding, indicating that the lock is being expanded by other threads, the current thread will spin and wait for the lock to expand. Here we need to note that although the lock is spinning, it will not always occupy the cpu resources. At intervals, it will give up the cpu resources through os:: NakedField method, or The lock is suspended by the park method; if other threads complete the expansion of the lock, they exit the spin and return.
5. If the current state is a lightweight lock, that is, the lock identification bit is 00, the expansion process is as follows:
Through omAlloc method, an available ObjectMonitor monitor is obtained and monitor data is reset. Through CAS, Mark Word is set to mark OopDesc: INFLATING to indicate that the current lock is expanding. If CAS fails, other threads have set Mark Word to mark OopDesc: INFLATING at the same time. The current threads spin to wait for expansion to complete. If CAS succeeds, set the fields of monitor: _header, _owner, and _object, and return.
6. If there is no lock, reset the monitor value.
This is from the jvm source code to analyze the lock expansion process.
Next, our case begins to analyze the process of biased lock (batch biased, batch revoked), lightweight lock, heavyweight lock and expansion:
Bias lock:
A biased lock is a synchronization code that has been accessed by a thread, which automatically acquires the lock and reduces the cost of acquiring the lock.
In most cases, locks are always acquired multiple times by the same thread, and there is no multi-threaded competition, so biased locks appear. The goal is to improve performance when only one thread executes synchronous code blocks.
When a thread accesses a synchronous block of code and acquires a lock, the lock-biased thread ID is stored in Mark Word. Instead of locking and unlocking through CAS operations when a thread enters or exits a synchronization block, it detects whether bias locks directed to the current thread are stored in Mark Word. The bias lock is introduced to minimize unnecessary lightweight lock execution paths without multithreading competition, because the acquisition and release of lightweight locks depend on multiple CAS atomic instructions, while the bias lock only needs to rely on one CAS atomic instruction when replacing ThreadID.
The bias lock is released only when other threads try to compete for the bias lock, and the thread holding the bias lock will not actively release the bias lock. The revocation of biased locks requires waiting for the global security point (at which point no bytecode is executing), which first suspends the thread with biased locks to determine whether the lock object is locked. Remove the bias lock and return to the state of unlock-free (flag bit is "01") or lightweight (flag bit is "00").
Bias locks are enabled by default in JDK 6 and later JVMs. The biased lock can be closed by JVM parameters: -XX:-UseBiasedLocking=false, after which the program defaults to a lightweight lock state.
In the previous article, Synchronized biased lightweight lock heavyweight lock proof of java Concurrent note 3, it was said that biased locks were biased before they were locked without forbidding delay, but after the lock was completed, the synchronized code block was biased; after hashcode was calculated, biased locks could not be biased.
First, let's look at the code to prove that without calculating hashcode:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Create a class that has nothing:
public class TestDemo {}
public class DemoExample {
static TestDemo testDemo; public static void main(String[] args) throws Exception { //Sleep 50,000 MS here, cancel jvm default bias lock delay 4,000 MS Thread.sleep(5000); testDemo= new TestDemo(); //hash calculation? //testDemo.hashCode(); System.out.println("befor lock"); //No lock: biased lock? System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); synchronized (testDemo){ System.out.println("lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } System.out.println("after lock"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); }
}
Operation results:
befor lock
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
lock ing
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763) 4 4 (object header) 8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763) 4 4 (object header) 8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 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 results are as follows:
befor lock: The green color indicates that although the lock is biased, the yellow color indicates that no thread holds the lock (an object is biased when initialized).
Locking: The green color indicates a bias lock, and the yellow color indicates that the current thread has the lock.
after lock: The green color indicates the bias lock, and the yellow color indicates whether the current thread has the lock or the bias state; (After the bias lock exits the lock, it is still the bias state.)
When an object is initialized by jvm, if no bias lock delay is enabled, it will be judged whether the object can be biased, if it can be biased, whether to exit the synchronization code block or biased lock.
2. After hashcode calculation, the following results will be output (that is, the testDemo.hashCode() of the code removes the comments and performs hashcode operation):
befor lock
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
lock ing
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f8 28 4b 0c (11111000 00101000 01001011 00001100) (206252280) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
after lock
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 80 80 ac (00000101 10000000 10000000 10101100) (-1400864763) 4 4 (object header) 8d 7f 00 00 (10001101 01111111 00000000 00000000) (32653) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 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 result shows that it is not biased lock, which means that the object can not be biased after hashcode has been calculated.
Specifically, when a thread is locked, if the lock object supports biased locking, the Java virtual opportunity records the address of the current thread in the tag field of the lock object through CAS operation, and sets the last three tag fields to: 101;
In the next run, whenever a thread requests the lock, the Java virtual machine only needs to determine whether the last three are in the lock object tag field: 101, whether the address of the current thread is included, and whether the epoch value is the same as the epoch value of the class of the lock object. If both are satisfied, the current thread holds the bias lock and can return directly.
What is the epoch value here?
Let's start with the removal of biased locks. When the thread requesting a lock does not match the thread address maintained by the lock object tag field (and the epoch value is equal, if not equal, then the current thread can biase the lock to itself), the Java virtual machine needs to revoke the biased lock. This revocation process is cumbersome, requiring threads holding biased locks to reach a safe point, and then replacing biased locks with lightweight locks.
If the total number of revocations for a class of lock objects exceeds a threshold (corresponding to the jvm parameter - XX: BiasedLocking Bulk Rebias Threshold, default is 20), then the Java Virtual Opportunity declares that the class's biased lock is invalid; (In this case, batch biased)
JVM source code:
1
2
3
4
5
product(intx, BiasedLockingBulkRebiasThreshold, 20, \
"Threshold of number of revocations per type to try to " \ "rebias all objects in the heap of that type") \ range(0, max_intx) \ constraint(BiasedLockingBulkRebiasThresholdFunc,AfterErgo) \
The specific approach is to maintain an epoch value in each class, which you can understand as generational biased locks. When setting bias locks, the Java virtual machine needs to copy the epoch value into the tag field of the lock object.
When a class's bias lock is declared invalid, the Java Virtual Machine actually adds the epoch value of this class to 1, indicating that the previous generation's bias lock has failed. The new bias lock needs to copy the new epoch value.
To ensure that currently biased and locked threads do not lose their locks, the Java virtual machine needs to traverse the Java stack of all threads, find out the locked instances of this class, and add the epoch value in their tag fields to 1. This operation requires all threads to be in a safe point state.
If the total number of revocations exceeds another threshold (corresponding to the jvm parameter - XX: BiasedLocking Bulk Revoke Threshold, default value is 40), then Java Virtual Opportunity considers this class no longer suitable for biased locks. At this point, the Java Virtual Opportunity revokes the biased locks of this class of instances, and directly sets lightweight locks for this class of instances in the subsequent locking process (in this case, biased bulk revocation)
JVM source code:
1
2
3
4
5
product(intx, BiasedLockingBulkRevokeThreshold, 40, \
"Threshold of number of revocations per type to permanently " \ "revoke biases of all objects in the heap of that type") \ range(0, max_intx) \ constraint(BiasedLockingBulkRevokeThresholdFunc,AfterErgo)
Next, we analyze two batch-biased cases (prohibiting biased lock delays: - XX:+UseBiased Locking-XX: Biased Locking Startup Delay = 0):
Case 1:
java code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class TestDemo {
}
public class DemoExample4 {
public static void main(String[] args) throws InterruptedException { test1(); }
public class DemoExample5 {
public static void main(String[] args) throws InterruptedException { test1(); } /** * Prove batch bias only * @throws InterruptedException */ public static void test1() throws InterruptedException { List<TestDemo> list = new ArrayList<>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread(()->{ System.out.println("Before locking get(0) Should be unlocked and biased "+ ClassLayout.parseInstance(list.get(0)).toPrintable()); for (TestDemo a:list ) { synchronized (a){ System.out.print("Lock up >"); } } System.out.println(); System.out.println("After locking get(0) Should be biased lock"+ClassLayout.parseInstance(list.get(0)).toPrintable()); try { TimeUnit.SECONDS.sleep(1000);//Threads are not allowed to die here to prevent thread ID reuse } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); TimeUnit.SECONDS.sleep(5); Thread t2 = new Thread(()->{ for (int i = 0; i < 40; i++) { TestDemo a = list.get(i); synchronized (a){ System.out.print("Lock up >"); } if (i==18){ System.out.println(); System.out.println("After locking get(18) Should be unlocked (lightweight lock release) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==19){ //Begin to be biased System.out.println(); System.out.println("After locking get(19) Should be biased lock "+ClassLayout.parseInstance(list.get(i)).toPrintable()); System.out.println("After locking get(0) Should be unlocked (lightweight lock release) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); System.out.println("After locking get(99) It should be lock bias. t1 "+ClassLayout.parseInstance(list.get(99)).toPrintable()); } if (i==20){ System.out.println(); System.out.println("After locking get(20) Should be biased lock "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } } }); t2.start(); }
}
Operation and analysis results:
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking
After locking, get(0) should be biased towards locking com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking
After locking, get(18) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Lock >
After locking, get(19) should be biased towards locking com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(0) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(99) should be biased towards t1 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 30 8a 73 (00000101 00110000 10001010 01110011) (1938436101) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Lock >
After locking, get(20) should be biased towards locking com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 41 0b 75 (00000101 01000001 00001011 01110101) (1963671813) 4 4 (object header) c4 7f 00 00 (11000100 01111111 00000000 00000000) (32708) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking
Case 2:
java code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class TestDemo {
}
public class DemoExample7 {
public static void main(String[] args) throws Exception { List<TestDemo> list = new ArrayList<>(); //Initialization data for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread() { String name = "1"; public void run() { System.out.printf(name); for (TestDemo a : list) { synchronized (a) { if (a == list.get(10)) { System.out.println("t1 Expectation is biased lock" + 10 + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); Thread.sleep(5000); System.out.println("main Expectation is biased lock" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); Thread t2 = new Thread() { String name = "2"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); // In order to lock again after the batch bias occurs, hack used lightweight lock objects in front of it. if (i == 20) { a = list.get(9); } synchronized (a) { if (i == 10) { //Objects that have been revoked by biased locks and used lightweight locks are released in a 001 unlocked state System.out.println("t2 i=10 get(1)Expected to be unlocked" + ClassLayout.parseInstance(list.get(1)).toPrintable()); //Lightweight locks are used because there is no competition with object a used alternately with t1, but the biased locks are biased and the biased conditions are not satisfied. System.out.println("t2 i=10 get(i) Expected lightweight lock " + i + ClassLayout.parseInstance(a).toPrintable()); } if (i == 19) { //Objects that have undergone biased lock revocation and use lightweight locks after a batch biased occurrence. Will not affect the existing state still remains 001 System.out.println("t2 i=19 get(10)Expected to be unlocked" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); //Once the bias condition is satisfied, the biased object can reuse the bias lock to point the thread id to the current thread, 101 System.out.println("t2 i=19 get(i) 20 Expected Bias Locks Satisfying Heavy Bias Conditions " + i + ClassLayout.parseInstance(a).toPrintable()); //When the bias condition is satisfied, the object that has been biased to lock is still biased to thread 1 because the revocation of the bias lock occurs at the next lock. This object is not synchronized here, so it is still biased toward t1 System.out.println("t2 i=19 get(i) The bias condition 20 is satisfied, but the object behind it is not locked, so it is still biased. t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable()); } if (i == 20) { //After satisfying the bias condition, the object that used the lightweight lock before locking again is still the lightweight lock, proving that the bias state is only for the bias lock. Upgraded locks do not return to biased locks System.out.println("t2 i=20 Once the bias condition is satisfied, the object previously set as unlocked is unbiased. Here, the lightweight lock is used. get(9)Expect lightweight locks " + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t2.start(); Thread.sleep(5000); }
}
Operation and analysis results:
t1 is expected to bias lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main expects to bias lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
2t2i = 10 get (1) is expected to be unlocked com.boke.TestDemo 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) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t2 i=10 get(i) Expected lightweight lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 69 42 08 (00001000 01101001 01000010 00001000) (138569992) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T2 I = 19 get (10) is expected to be unlocked 10com.boke.TestDemo 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) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T2 I = 19 get (i) satisfies the biased condition 20 and expects to biase to lock 19com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 71 95 ae (00000101 01110001 10010101 10101110) (-1365937915) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T2 I = 19 get (i) satisfies the bias condition 20, but the following object is not locked, so it still biases to t1 19com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 78 86 af (00000101 01111000 10000110 10101111) (-1350141947) 4 4 (object header) f6 7f 00 00 (11110110 01111111 00000000 00000000) (32758) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After T2 I = 20 satisfies the bias condition, the object previously set as unlocked state is not biased. Here the lightweight lock get(9) is expected to be lightweight lock com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 69 42 08 (00001000 01101001 01000010 00001000) (138569992) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 05 d6 00 f8 (00000101 11010110 00000000 11111000) (-134162939) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Next, we analyze two cases of batch biased revocation (where biased lock delay is prohibited: - XX:+UseBiased Locking-XX: Biased Locking Startup Delay = 0):
Case 1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
public class TestDemo {
}
public class DemoExample6 {
public static void main(String[] args) throws InterruptedException { test2(); } /** * Proof bias revocation * @throws InterruptedException */ public static void test2() throws InterruptedException { List<TestDemo> list = new ArrayList<TestDemo>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); } Thread t1 = new Thread(()->{ System.out.println("Before locking get(0) Should be unlocked and biased "+ClassLayout.parseInstance(list.get(0)).toPrintable()); for (TestDemo a:list ) { synchronized (a){ System.out.print("Lock up >"); } } System.out.println(); System.out.println("After locking get(0) Should be biased lock"+ClassLayout.parseInstance(list.get(0)).toPrintable()); try { TimeUnit.SECONDS.sleep(1000);//Threads are not allowed to die here to prevent thread ID reuse } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); TimeUnit.SECONDS.sleep(5); Thread t2 = new Thread(()->{ for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); synchronized (a){ System.out.println(Thread.currentThread().getId()+"Lock up >"); } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (i==9){//Here's exactly the 19th lock-up (also the 19th lock-biased upgrade) System.out.println(); System.out.println("After locking get(9) Should be unlocked (lightweight lock release) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==10){//It happens to be the 21st lock here. System.out.println(); System.out.println("After locking get(10) It should be lock bias. t2 "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==50){//50 Begins to upgrade to lightweight lock (also the 21st biased lock upgrade) System.out.println(); System.out.println("After locking get(50) No Lock (Lightweight Lock Release) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==59){//60 (also the 39th lock-biased upgrade) System.out.println(); System.out.println("After locking get(59) No Lock (Lightweight Lock Release) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); } if (i==69){//69 (also the 59th lock-biased upgrade) System.out.println(); System.out.println("After locking get(69) No Lock (Lightweight Lock Release) "+ClassLayout.parseInstance(list.get(i)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("New objects of this type that are biased toward undo will no longer biased toward any threads "+ClassLayout.parseInstance(a1).toPrintable()); } } } }); Thread t3 = new Thread(()->{ for (int i = 99; i >= 0; i--) { TestDemo a = list.get(i); synchronized (a){ System.out.println(Thread.currentThread().getId()+"Lock up >"); } try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } /** * Focus: Focus on Revocation */ if (i==40){//40 Upgraded to Lightweight Lock (also the 40th biased lock upgraded, when biased revocation occurs) System.out.println(); System.out.println("After locking get("+i+") Should be unlocked (lightweight lock release) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("New objects of this type that are biased toward undo will no longer biased toward any threads "+ClassLayout.parseInstance(a1).toPrintable()); } } if (i==30){//39 Upgraded to Lightweight Lock (also the 42nd Deviated Lock Upgraded) System.out.println(); System.out.println("After locking get("+i+") Should be unlocked (lightweight lock release) "+ClassLayout.parseInstance(list.get(0)).toPrintable()); TestDemo a1 = new TestDemo(); synchronized (a1){ System.out.println("New objects of this type that are biased toward undo will no longer biased toward any threads "+ClassLayout.parseInstance(a1).toPrintable()); } } } }); t2.start(); TimeUnit.MILLISECONDS.sleep(50); t3.start(); }
}
Running results (interception part):
Before locking, get(0) should be unlocked and biased to com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > Lock > > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking > Locking
After locking, get(0) should be biased towards locking com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 84 08 (00000101 11100000 10000100 00001000) (142925829) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(9) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 locks >
After locking, get(90) should be biased towards t3com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 89 01 0c (00000101 10001001 00000001 00001100) (201427205) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(10) should be biased towards t2 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 b1 0a 08 (00000101 10110001 00001010 00001000) (134918405) 4 4 (object header) b1 7f 00 00 (10110001 01111111 00000000 00000000) (32689) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 locks >
After locking, get(89) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(50) unlock-free (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 locks >
After locking, get(49) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(59) unlock-free (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 locks >
After locking, get(40) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Any new object of this type after an undo occurs will no longer be biased towards any thread com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 18 a6 09 (01001000 00011000 10100110 00001001) (161880136) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
After locking, get(69) unlock-free (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Any new object of this type after an undo occurs will no longer be biased towards any thread com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 50 e8 95 09 (01010000 11101000 10010101 00001001) (160819280) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
15 locks >
After locking, get(30) should be unlocked (lightweight lock release) com.boke.TestDemo 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 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Any new object of this type after an undo occurs will no longer be biased towards any thread com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 48 18 a6 09 (01001000 00011000 10100110 00001001) (161880136) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Case 2:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
public class TestDemo {
}
public class DemoExample8 {
public static void main(String[] args) throws Exception { List<TestDemo> list = new ArrayList<>(); List<TestDemo> list2 = new ArrayList<>(); List<TestDemo> list3 = new ArrayList<>(); for (int i = 0; i < 100; i++) { list.add(new TestDemo()); list2.add(new TestDemo()); list3.add(new TestDemo()); } //Biased Lock System.out.println("Initial state" + 10 + ClassLayout.parseClass(TestDemo.class).toPrintable()); Thread t1 = new Thread() { String name = "1"; public void run() { System.out.printf(name); for (TestDemo a : list) { synchronized (a) { if (a == list.get(10)) { //Biased Lock System.out.println("t1 Expectation is biased lock" + 10 + ClassLayout.parseInstance(a).toPrintable()); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); Thread.sleep(5000); //Biased Lock System.out.println("main Expectation is biased lock" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable()); Thread t2 = new Thread() { String name = "2"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list.get(i); synchronized (a) { if (a == list.get(10)) { System.out.println("t2 i=10 get(1)Expected to be unlocked" + ClassLayout.parseInstance(list.get(1)).toPrintable());//Biased Lock System.out.println("t2 i=10 get(10) Expected lightweight lock " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } if (a == list.get(19)) { System.out.println("t2 i=19 get(10)Expected to be unlocked" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//Biased Lock System.out.println("t2 i=19 get(19) 20 Expected Bias Locks Satisfying Heavy Bias Conditions " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock System.out.println("The cumulative object revocation of the class reaches 20"); } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t2.start(); Thread.sleep(5000); Thread t3 = new Thread() { String name = "3"; public void run() { System.out.printf(name); for (TestDemo a : list2) { synchronized (a) { if (a == list2.get(10)) { System.out.println("t3 Expectation is biased lock" + 10 + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t3.start(); Thread.sleep(5000); Thread t4 = new Thread() { String name = "4"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list2.get(i); synchronized (a) { if (a == list2.get(10)) { System.out.println("t4 i=10 get(1)Expected to be unlocked" + ClassLayout.parseInstance(list2.get(1)).toPrintable());//Biased Lock System.out.println("t4 i=10 get(10) Expected Lightweight Locks under Preferred Conditions 20 Currently Not Satisfied " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } if (a == list2.get(19)) { System.out.println("t4 i=19 get(10)Expected to be unlocked" + 10 + ClassLayout.parseInstance(list2.get(10)).toPrintable());//Biased Lock System.out.println("t4 i=19 get(19) At present, it meets the bias condition 20, but A Class object revocation reaches 40 expected lightweight locks " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock System.out.println("The cumulative object revocation of the class reaches 40"); } if (a == list2.get(20)) { System.out.println("t4 i=20 get(20) Currently meet the biased condition 20 expected lightweight lock " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } } } } }; t4.start(); Thread.sleep(5000); System.out.println("main Expectation is biased lock" + 10 + ClassLayout.parseInstance(list3.get(0)).toPrintable());//Biased Lock Thread t5 = new Thread() { String name = "5"; public void run() { System.out.printf(name); for (TestDemo a : list3) { synchronized (a) { if (a == list3.get(10)) { System.out.println("t5 Expected to be lightweight locks, object revocation of classes up to 40 can not be biased locks" + 10 + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t5.start(); Thread.sleep(5000); System.out.println("main Expectation is biased lock" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//Biased Lock Thread t6 = new Thread() { String name = "6"; public void run() { System.out.printf(name); for (int i = 0; i < 100; i++) { TestDemo a = list3.get(i); synchronized (a) { if (a == list3.get(10)) { System.out.println("t6 i=10 get(1)Expected to be unlocked" + ClassLayout.parseInstance(list3.get(1)).toPrintable());//Biased Lock System.out.println("t6 i=10 get(10) Expected lightweight lock " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } if (a == list3.get(19)) { System.out.println("t6 i=19 get(10)Expected to be unlocked" + 10 + ClassLayout.parseInstance(list3.get(10)).toPrintable());//Biased Lock System.out.println("t6 i=19 get(19) The bias condition 20 is satisfied, but the object revocation of the class is up to 40, so the bias lock can not be used. " + i + ClassLayout.parseInstance(a).toPrintable());//Biased Lock } } } try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t6.start(); Thread.sleep(5000); System.out.println("Because the number of unlocks reached the default BiasedLockingBulkRevokeThreshold=40 The object instantiated here is unlocked" + ClassLayout.parseInstance(new TestDemo()).toPrintable());//Biased Lock
System.out.println("Undo Backward State"+10 + ClassLayout.parseInstance (new TestDemo (). toPrintable ();//Bias Lock
}
}
Operation results:
Initial state 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
1t1 is expected to be biased towards lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 86 8e (00000101 11100000 10000110 10001110) (-1903763451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main expects to bias lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 86 8e (00000101 11100000 10000110 10001110) (-1903763451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
2t2i = 10 get (1) is expected to be unlocked com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t2 i=10 get(10) Expected lightweight lock 10 com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 99 7a 03 (00001000 10011001 01111010 00000011) (58366216) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T2 I = 19 get (10) is expected to be unlocked 10 com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T2 I = 19 get (19) satisfies the biased condition 20 and expects to biase to lock 19com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 09 90 91 (00000101 00001001 10010000 10010001) (-1852831483) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 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 cumulative object revocation of the class reaches 20
3t3 is expected to be biased towards lock 10com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 09 89 90 (00000101 00001001 10001001 10010000) (-1870067451) 4 4 (object header) ec 7f 00 00 (11101100 01111111 00000000 00000000) (32748) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
4t4i = 10 get (1) is expected to be unlocked com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t4 i=10 get(10) Currently does not meet the expected lightweight lock 10com.boke.TestDemo object internals under the biased condition 20:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T4 I = 19 get (10) is expected to be unlocked 10com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t4 i=19 get(19) Currently meets the bias condition 20, but the cumulative revocation of class A objects reaches 40 expected lightweight locks 19com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 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 cumulative object revocation of the class reaches 40
t4 i=20 get(20) Currently meets the biased condition 20. Expected lightweight lock 20com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main expects to biase to lock 10com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
5t5 is expected to be a lightweight lock. The cumulative revocation of class A objects reaches 40 and can not be biased to lock 10com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 f9 9a 03 (00001000 11111001 10011010 00000011) (60487944) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
main expects to biase to lock 10com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
6t6i = 10 get (1) is expected to be unlocked com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t6 i=10 get(10) Expects lightweight lock 10com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 29 ab 03 (00001000 00101001 10101011 00000011) (61548808) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T6 I = 19 get (10) is expected to be unlocked 10com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
T6 I = 19 get (19) satisfies the bias condition 20, but the object revocation of class A reaches 40 and can not be biased to lock 19com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 08 29 ab 03 (00001000 00101001 10101011 00000011) (61548808) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Because the number of unlocks revoked by the class reaches the default BiasedLocking Bulk RevokeThreshold = 40, the object instantiated here is unlocked state com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Undo the backward-biased state 10com.boke.TestDemo 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) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617)
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 above cases confirm the batch bias and the batch revocation of the biased locks. Next, we will explain the lightweight locks.
Lightweight lock:
When a lock is biased, accessed by another thread, the biased lock will be upgraded to a lightweight lock, and other threads will try to acquire the lock in the form of spin without blocking, thus improving performance.
When the code enters the synchronization block, if the lock state of the synchronization object is unlocked (lock flag bit is "01" state, bias lock is "0"), the virtual machine will first create a space called Lock Record in the stack frame of the current thread to store the copy of the current Mark Word of the lock object. Then copy Mark Word from the object header to the lock record.
When the copy is successful, the virtual machine will use the CAS operation to attempt to update the object's Mark Word to a pointer to Lock Record and point the owner pointer in Lock Record to the object's MarkWord.
If the update action succeeds, the thread owns the object's lock, and the lock flag of the object Mark Word is set to "00" to indicate that the object is in a lightweight lock state.
If the update operation of lightweight lock fails, the virtual machine first checks whether the Mark Word of the object points to the stack frame of the current thread. If it means that the current thread already has the lock of the object, it can go directly into the synchronization block to continue executing. Otherwise, it means that multiple threads compete for the lock.
If there is currently only one waiting thread, the thread waits by spin. But when the spin exceeds a certain number of times, or a thread holds a lock, a spin, and a third visit, the lightweight lock is upgraded to a heavy lock.
Multiple threads request the same lock at different times, that is, there is no lock competition. In this case, the Java Virtual Machine uses lightweight locks to avoid blocking and waking up of heavy locks.
Reducing the performance loss of traditional locks using OS mutexes without lock competition
When competition is fierce, lightweight locks do a lot of extra work, resulting in performance degradation.
You can think of requesting the same lock when two threads execute alternately
An example of expansion from bias lock to lightweight lock is analyzed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class TestDemo {
}
public class DemoExample9 {
public static void main(String[] args) throws Exception { TestDemo testDemo = new TestDemo(); //Child Thread Thread t1 = new Thread(){ @Override public void run() { synchronized (testDemo){ System.out.println("t1 lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }; t1.join(); //Main thread synchronized (testDemo){ System.out.println("main lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } }
}
Running results (when two threads execute alternately):
main lock ing
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) e8 48 95 09 (11101000 01001000 10010101 00001001) (160778472) 4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Heavy Lock:
When multiple threads compete for the same lock, the virtual opportunity blocks threads that failed to lock and wakes them up when the target lock is released.
The blocking and wake-up of Java threads are done by operating system: os pthread_mutex_lock();
When upgraded to a heavyweight lock, the state value of the lock flag changes to "10", and the pointer stored in Mark Word is the pointer to the heavyweight lock, at which time the thread waiting for the lock will enter the blocking state.
An example of expansion from a lightweight lock to a heavyweight lock is analyzed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestDemo {
}
public class DemoExample9 {
public static void main(String[] args) throws Exception { TestDemo testDemo = new TestDemo();
Thread t1 = new Thread(){ @Override public void run() { synchronized (testDemo){ System.out.println("t1 lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } } }; t1.start(); synchronized (testDemo){ System.out.println("main lock ing"); System.out.println(ClassLayout.parseInstance(testDemo).toPrintable()); } }
}
Operation results:
main lock ing
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a ad 00 b0 (01011010 10101101 00000000 10110000) (-1342132902) 4 4 (object header) cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
t1 lock ing
com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a ad 00 b0 (01011010 10101101 00000000 10110000) (-1342132902) 4 4 (object header) cf 7f 00 00 (11001111 01111111 00000000 00000000) (32719) 8 4 (object header) a0 c1 00 f8 (10100000 11000001 00000000 11111000) (-134168160) 12 4 (loss due to the next object alignment)
Let's talk about how the Java Virtual Machine distinguishes lightweight locks from heavy locks.
When locking is performed, the Java Virtual Opportunity determines whether it is already a heavyweight lock. If not, it will draw a space in the current stack frame of the current thread as the lock record of the lock, and copy the tag field of the lock object into the lock record.
Then, the Java Virtual Opportunity attempts to replace the tag field of the lock object with a CAS (compare-and-swap) operation. Explain here that CAS is an atomic operation that compares whether the value of the target address is equal to the expected value and, if equal, replaces it with a new value.
Assume that the tag field of the current lock object is X... XYZ, Java Virtual Opportunity compares whether the field is X... X01. If so, replace it with the address of the lock record just allocated. Because of memory alignment, the last two are 00. At this point, the thread has successfully acquired the lock and can continue to execute.
If it weren't X... X01, then there are two possibilities. First, the thread retrieves the same lock repeatedly. At this point, the Java virtual opportunity zeroes the lock record to represent that the lock is retrieved repeatedly. Second, other threads hold the lock. At this point, the Java Virtual Opportunity expands the lock into a heavyweight lock and blocks the current thread.
When unlocking, if the current lock record (you can imagine all lock records of a thread as a stack structure, each time a lock is pressed into a lock record, unlock pops up a lock record, the current lock record refers to the lock record at the top of the stack) has a value of 0, then it represents repeated entry into the same lock and returns directly. Yes.
Otherwise, the Java Virtual Opportunity attempts to use CAS to compare whether the value of the tag field of the lock object is the address of the current lock record. If so, it is replaced by the value in the lock record, which is the original tag field of the lock object. At this point, the thread has completed
Work releases the lock.
If not, it means that the lock has been expanded into a heavy-duty lock. At this point, the Java virtual opportunity enters the release process of the heavyweight lock, waking up the thread blocked by competing for the lock.
So far this chapter has finished the expansion process of locks:
To sum up:
Biased locks only use CAS operations on the first request, recording the address of the current thread in the tag field of the lock object. During subsequent runs, the lock operation of the thread holding the biased lock will be returned directly. It is designed for situations where locks are held only by the same thread.
The lightweight lock uses CAS operation, replacing the tag field of the lock object with a pointer, pointing to a space on the current thread stack, and storing the original tag field of the lock object. It aims at the situation that multiple threads apply for the same lock in different time periods.
Heavy locks block and wake up threads that request locking. It aims at the situation that multiple threads compete for the same lock at the same time. Java virtual machines adopt adaptive spin to avoid thread blocking and waking up when faced with very small synchronized code blocks.
After talking about the expansion process of the lock, will there be any degradation of the lock?
I found this comment in the hotspot source code:
// We create a list of in-use monitors for each thread.
//
// deflate_thread_local_monitors() scans a single thread's in-use list, while
// deflate_idle_monitors() scans only a global list of in-use monitors which
// is populated only as a thread dies (see omFlush()).
//
// These operations are called at all safepoints, immediately after mutators
// are stopped, but before any objects have moved. Collectively they traverse
// the population of in-use monitors, deflating where possible. The scavenged
// monitors are returned to the monitor free list.
//
// Beware that we scavenge at every stop-the-world point. Having a large
// number of monitors in-use could negatively impact performance. We also want
// to minimize the total # of monitors in circulation, as they incur a small
// footprint penalty.
//
// Perversely, the heap size -- and thus the STW safepoint rate --
// typically drives the scavenge rate. Large heaps can mean infrequent GC,
// which in turn can mean large(r) numbers of objectmonitors in circulation.
// This is an unfortunate aspect of this design.
// Lock degradation does occur. When a JVM enters a SafePoint, it checks for an idle Monitor and then tries to downgrade.
Interested Big Boys can https://hg.openjdk.java.net/jdk/jdk/file/896e80158d35/src/hotspot/share/runtime/synchronizer.cpp In the link:
Consider that deflate_idle_monitors are the entry point for analyzing lock degrading logic, and this part of the behavior is continually improving, because the logic runs within the security point, and improper handling may prolong the time of JVM pause (STW, stop-the-world).
Original address https://www.cnblogs.com/yuhangwang/p/11295940.html