This paper is divided into two parts:

1. Lightweight lock acquisition process

2. Lightweight lock release process

The JVM version I see is JDK8u, the specific version number and code can be seen here.

Lightweight lock acquisition process

Let’s begin the lightweight lock acquisition process analysis in bytecodeInterpreter. Cpp# 1816.

CASE(_monitorenter): { oop lockee = STACK_OBJECT(-1); .if(entry ! = NULL) { ... / / omitted in the code above if the CAS fails will call to InterpreterRuntime: : monitorenter / / traditional lightweight lockingif(! Return first () {return first (); return first (); return first (); return first (); // Set entry to the Lock Record -> Lock ()->set_displaced_header(product); bool call_vm = UseHeavyMonitors;if(call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) ! Product of the herbivre product Is herbivre; // if the product of the herbivre product Is herbivre; // if the product of the herbivre product Is herbivre; // if the product of the herbivre product Is herbivrecase?
        if(! call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) { entry->lock()->set_displaced_header(NULL); }else{/ / CAS operation failure is called monitorenter CALL_VM (InterpreterRuntime: : monitorenter (THREAD, entry), handle_exception); } } } UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); }else {
    istate->set_msg(more_monitors);
    UPDATE_PC_AND_RETURN(0); // Re-execute
  }
}
Copy the code

Success is false if the lock object is not in biased mode or has been biased to another thread. In this case, a mark Word in the unlocked state will be constructed and set into the Lock Record. We call the field storing the object mark Word in the Lock Record as “product Mark Word”. If the current lock is not locked, the CAS fails. If this is a Lock reentrant, set the Product of the Lock Record to null. Let’s look at a demo, and in the demo, we’ll repeat this 3 times to get the lock,

synchronized(obj){
    synchronized(obj){
    	synchronized(obj){
    	}
    }
}
Copy the code

Assuming that the Lock state is lightweight, the following figure reflects the state of the Mark Word and the Lock Record in the thread stack. It can be seen that the thread stack on the right contains three Lock records pointing to the current Lock object. Where the highest bit of the stack Lock Record is allocated when the Lock is acquired for the first time. The value of herbier’s herbier Mark Word is the Mark word before the function is added to the Lock object, and the Lock reentry will allocate a Lock Record of herbier’s herbier Mark Word as null in the thread stack.



Why does the JVM choose to add bug Mark Word null Lock Record to the thread stack to represent the reentrant count? First of all, the number of lock reentrant times must be recorded, because each unlock needs to be locked once. The lock will be released only when the number of lock reentrant times is equal to the number of lock reentrant times. A simple solution is to record the lock reentrant count in the mark Word in the object header, but the mark Word is too small to store this information. The alternative is to just create a Lock Record and Record the number of reentries in it. The reason Hotspot doesn’t do this I guess is because of the efficiency impact: each time a reentrant obtains a Lock, it needs to traverse the stack of that thread to find the corresponding Lock Record and change its value.

So Hotspot eventually chooses to add a Lock Record each time it acquires a Lock to represent the Lock reentrant.

The next see InterpreterRuntime: : monitorenter method

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
  ...
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (UseBiasedLocking) {
    // Retry fast entry if bias is revoked to avoid unnecessary inflation
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else{ ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); }... IRT_ENDCopy the code

The process of fast_enter has been analyzed in the biased Lock article. If the current mode is biased and the biased thread is still using the Lock, it will change the mark word of the Lock to the state of lightweight Lock, and change the Lock Record in the biased thread stack to the corresponding form of lightweight Lock. The code location is biasedLocking. Cpp# 212.

// All Lock Record GrowableArray<MonitorInfo*>* cached_monitor_info = traverses the thread stack while the thread is still alive get_or_compute_monitor_info(biased_thread); BasicLock* highest_lock = NULL;for(int i = 0; i < cached_monitor_info->length(); i++) { MonitorInfo* mon_info = cached_monitor_info->at(i); // If the corresponding Lock Record can be found, the biased thread is still executing the code in the synchronized code blockif(mon_info->owner() == obj) { ... // Need to upgrade to lightweight Lock, directly modify the bias of the thread stack Lock Record. To handle lock reentrantcaseWhere the Hermite of Lock Record is set to null The first Lock Record is reprocessed in the following code: markOop mark = markOopDesc::encode((BasicLock*) NULL); highest_lock = mon_info->lock(); highest_lock->set_displaced_header(mark); }else{... }}if(highest_lock ! = NULL) {// Change the first Lock Record to no Lock state, Highest_lock ->set_displaced_header(unbiased_prototype); highest_lock->set_displaced_header(prototype); obj->release_set_mark(markOopDesc::encode(highest_lock)); . }else{... }Copy the code

Let’s look at the slow_enter process.

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 there is no lock stateif(mark-> neutral()) {// Set the product of the Hermite product and replace the mark Word lock->set_displaced_header(mark);if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
      TEVENT (slow_enter: release stacklock) ;
      return; }}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"); Function of product product product is set to null lock->set_displaced_header(null); function of product product is set to null lock->set_displaced_header(null);return; }... Set_displaced_header (markOopDesc::unused_mark())); ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }Copy the code

Lightweight lock release process

CASE(_monitorexit): {
  oop lockee = STACK_OBJECT(-1);
  CHECK_NULL(lockee);
  // derefing's lockee ought to provoke implicit null check // find our monitor slot BasicObjectLock* limit = istate->monitor_base();  BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base(); // Iterate through the stack from low to high Lock Record while (most_recent! If ((most_recent)->obj() == lockee) {BasicLock* Lock = most_recent-> Lock (); markOop header = lock->displaced_header(); // Release Lock Record most_recent->set_obj(NULL); // In bias mode, just release the Lock Record. Otherwise, go through the release process of lightweight lock or heavyweight lock. lockee->mark()->has_bias_pattern()) { bool call_vm = UseHeavyMonitors; // header! If (header!) =NULL indicates that the product of the Hermite product is not reentrant. = NULL || call_vm) { if (call_vm || Atomic::cmpxchg_ptr(header, lockee->mark_addr(), lock) ! Most_recent ->set_obj(lockee); CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception); }} // Run the next command UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); } // Process the next Lock Record most_recent++; } // Need to throw illegal monitor state exception CALL_VM(InterpreterRuntime::throw_illegal_monitor_state_exception(THREAD), handle_exception); ShouldNotReachHere(); }Copy the code

Replace the product of the product’s product with the Mark Word in the object header when releasing the lightweight lock. If the CAS fails or heavyweight lock into InterpreterRuntime: : monitorexit method.

//%note monitor_1 IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)) Handle h_obj(thread, elem->obj()); . ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread); // Free entry. This must bedoneHere, since a pending exception might be installed on // Release Lock Record elem->set_obj(NULL); . IRT_ENDCopy the code

Monitorexit releases the Lock Record after calling slow_exit.

void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
  fast_exit (object, lock, THREAD) ;
}
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  ...
  markOop dhw = lock->displaced_header();
  markOop mark ;
  if(DHW == NULL) {// Reentrant lock, do nothing...return; } mark = object->mark() ; // If the product of the herbivist product is product of the herbivist product, CAS replaces the product header with the herbivist productif (mark == (markOop) lock) {
     assert (dhw->is_neutral(), "invariant");if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
        TEVENT (fast_exit: release stacklock) ;
        return; }} // This means that the heavyweight lock or unlock occurred during the contention, after expansion called the heavyweight lockexitMethods. ObjectSynchronizer::inflate(THREAD, object)->exit (true, THREAD) ;
}
Copy the code

If it is a lightweight lock, it will replace mark Word, otherwise it will inflate to a heavyweight lock and call exit. The logic will be explained in the heavyweight lock article.