The underlying implementation principle of synchronize bias lock

1. Meaning of bias lock

Unnecessary multi-path lock execution reduces the number of threads competing unnecessarily. In most cases, the lock not only does not have multi-threaded competition, but also the same thread always obtains the lock multiple times. In order to make the thread obtain the lock with lower performance cost, biased lock is introduced.

Bias lock is mainly used to optimize the competition of the same thread applying for the same lock multiple times, that is, when the object is regarded as a synchronous lock and a thread grabs the lock, set the thread ID of the thread, whether to bias lock setting 1, lock flag bit setting 01 and other information in Mark Word. At this time, Mark Word stores the bias lock status information.

At:

  • Create a thread and perform loop listening in the thread
  • Or single thread operation of a thread safe collection

The same thread needs to acquire and release the lock every time, and the switch between user state and kernel state will occur every time.

Scenario of obtaining bias lock:

A Lock Record is generated in its own thread stack, and then the Object Reference points to the object header. At this time, the Lock Record is connected with the object header:

① : first judge whether the Thread ID of Mar word has a value

  • If not, it means that the current resource is not occupied by other threads. Record the current thread ID and other information to Mark Word (this requires CAS. Multiple threads may modify Mark Word, and atomicity needs to be guaranteed)
  • If yes, it means that the current resource is occupied by the thread. You need to judge whether the thread is itself
    • If the thread ID is its own, it means that it can be re entered and obtained directly (at this time, a new lock record will continue to be generated in its own thread stack)
    • The thread ID is not its own, which indicates that other threads compete, and the thread currently holding the bias lock needs to be revoked. That is, the lock is released only when other threads try to obtain the bias lock

The acquisition and release of lightweight locks depend on multiple CAS operations, while biased locks only rely on one CAS replacement ThreadID.

Once multiple threads compete, the bias lock must be revoked, so:

The performance cost of revoking biased locks must be less than the performance cost of CAS atomic operations saved previously

Otherwise, the gain is not worth the loss!

JDK6 turns on the bias lock by default. The bias lock can be disabled through - XX:-UseBiasedLocking.

2. Acquisition of deflection lock

Bias lock entry, synchronizer Of cpp file

ObjectSynchronizer::fast_enter

By BiasedLocking::revoke_and_rebias implementation

2.1 markOop mark = obj->mark()

Get the markOop data mark of the object, that is, the Mark Word of the object header

2.2 judge whether mark is biased

The lock flag of the bias lock of mark is 01

2.3 judge the status of JavaThread in mark

  • If it points to the current thread, the synchronous code block is executed
  • If it is empty, go 4
  • If it points to other threads, go 5

2.4 implementation of CAS atomic instructions

Set the JavaThread in mark as the current thread ID.

If CAS succeeds, execute the synchronization code block, otherwise go 5.

2.5 failed to execute CAS

This indicates that there are currently multiple threads competing for locks. When the global safety point is reached, the thread that obtains the bias lock will be suspended, the bias lock will be revoked, and upgraded to a lightweight lock.

After the upgrade is completed, the thread blocked at the safe point continues to execute the synchronous code block.

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");

  // We can revoke the biases of anonymously-biased objects
  // efficiently enough that we should not cause these revocations to
  // update the heuristics because doing so may cause unwanted bulk
  // revocations (which are expensive) to occur.
  // step1
  markOop mark = obj->mark();
  if (mark->is_biased_anonymously() && !attempt_rebias) {
    // We are probably trying to revoke the bias of this object due to
    // an identity hash code computation. Try to revoke the bias
    // without a safepoint. This is possible if we can successfully
    // compare-and-exchange an unbiased header into the mark word of
    // the object, meaning that no other thread has raced to acquire
    // the bias of the object.
    markOop biased_value       = mark;
    markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
    markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
    if (res_mark == biased_value) {
      return BIAS_REVOKED;
    }
  } else if (mark->has_bias_pattern()) {
    Klass* k = obj->klass();
    markOop prototype_header = k->prototype_header();
    if (!prototype_header->has_bias_pattern()) {
      // This object has a stale bias from before the bulk revocation
      // for this data type occurred. It's pointless to update the
      // heuristics at this point so simply update the header with a
      // CAS. If we fail this race, the object's bias has been revoked
      // by another thread so we simply return and let the caller deal
      // with it.
      markOop biased_value       = mark;
      markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark);
      assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked");
      return BIAS_REVOKED;
    } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {
      // The epoch of this biasing has expired indicating that the
      // object is effectively unbiased. Depending on whether we need
      // to rebias or revoke the bias of this object we can do it
      // efficiently enough with a CAS that we shouldn't update the
      // heuristics. This is normally done in the assembly code but we
      // can reach this point due to various points in the runtime
      // needing to revoke biases.
      if (attempt_rebias) {
        assert(THREAD->is_Java_thread(), "");
        markOop biased_value       = mark;
        markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED_AND_REBIASED;
        }
      } else {
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;
        }
      }
    }
  }

  HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias);
  if (heuristics == HR_NOT_BIASED) {
    return NOT_BIASED;
  } else if (heuristics == HR_SINGLE_REVOKE) {
    Klass *k = obj->klass();
    markOop prototype_header = k->prototype_header();
    if (mark->biased_locker() == THREAD &&
        prototype_header->bias_epoch() == mark->bias_epoch()) {
      // A thread is trying to revoke the bias of an object biased
      // toward it, again likely due to an identity hash code
      // computation. We can again avoid a safepoint in this case
      // since we are only going to walk our own stack. There are no
      // races with revocations occurring in other threads because we
      // reach no safepoints in the revocation path.
      // Also check the epoch because even if threads match, another thread
      // can come in with a CAS to steal the bias of an object that has a
      // stale epoch.
      ResourceMark rm;
      if (TraceBiasedLocking) {
        tty->print_cr("Revoking bias by walking my own stack:");
      }
      EventBiasedLockSelfRevocation event;
      BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD, NULL);
      ((JavaThread*) THREAD)->set_cached_monitor_info(NULL);
      assert(cond == BIAS_REVOKED, "why not?");
      if (event.should_commit()) {
        event.set_lockClass(k);
        event.commit();
      }
      return cond;
    } else {
      EventBiasedLockRevocation event;
      VM_RevokeBias revoke(&obj, (JavaThread*) THREAD);
      VMThread::execute(&revoke);
      if (event.should_commit() && (revoke.status_code() != NOT_BIASED)) {
        event.set_lockClass(k);
        // Subtract 1 to match the id of events committed inside the safepoint
        event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
        event.set_previousOwner(revoke.biased_locker());
        event.commit();
      }
      return revoke.status_code();
    }
  }

  assert((heuristics == HR_BULK_REVOKE) ||
         (heuristics == HR_BULK_REBIAS), "?");
  EventBiasedLockClassRevocation event;
  VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD,
                                (heuristics == HR_BULK_REBIAS),
                                attempt_rebias);
  VMThread::execute(&bulk_revoke);
  if (event.should_commit()) {
    event.set_revokedClass(obj->klass());
    event.set_disableBiasing((heuristics != HR_BULK_REBIAS));
    // Subtract 1 to match the id of events committed inside the safepoint
    event.set_safepointId(SafepointSynchronize::safepoint_counter() - 1);
    event.commit();
  }
  return bulk_revoke.status_code();
}

3. Cancellation of deflection lock

The thread holding the biased lock will release the lock only when other threads try to compete for the biased lock.

BiasedLocking::revoke_at_safepoint implementation:

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
  assert(SafepointSynchronize::is_at_safepoint(), "must only be called at safepoint");
  oop obj = h_obj();
  HeuristicsResult heuristics = update_heuristics(obj, false);
  if (heuristics == HR_SINGLE_REVOKE) {
    revoke_bias(obj, false, false, NULL, NULL);
  } else if ((heuristics == HR_BULK_REBIAS) ||
             (heuristics == HR_BULK_REVOKE)) {
    bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
  }
  clean_up_cached_monitor_info();
}
  1. The revocation action of biased lock must wait for the global safety point (the pause point that will block all threads during GC)
  2. Pause the thread with biased lock and judge whether the lock object is locked
  3. Cancel the bias lock and return to the state of no lock (flag bit 01) or lightweight lock (flag bit 00)

The bias lock is enabled by default after Java 1.6, but it is activated only a few seconds after the application starts. The delay can be turned off:

-XX:BiasedLockingStartupDelay=0

If it is determined that all locks in the application are normally in a competitive state, the bias lock can be turned off:

XX:-UseBiasedLocking=false((on by default)

Release of deflection lock

Traverse all lock records of the thread stack and cut off the ObjectReference, that is, ObjectReference = null

Set the ObjectReference to null, but the Mark Word of the object header of the lock object remains unchanged and still biases towards the previous thread, so the lock is not released. Indeed, when the thread exits the critical area, it does not release the biased lock. This is to: when it needs to obtain the lock again, it only needs to simply judge whether it is reentry to obtain the lock quickly, instead of CAS every time, This is also the core of biased locking, which is efficient when only one thread accesses the lock.

summary

  • When lock resource access occurs, a Lock Record will be generated in the current thread stack, and the ObjectReference will point to the Mark Word of the object header of the lock object. This setting may be multi-threaded and requires CAS operation
  • In the case of multiple threads competing for the same lock resources, the revocation of biased locks will affect the efficiency
  • The reentry count of biased lock depends on the number of lock records in the thread stack
  • Partial lock revocation fails and will eventually be upgraded to a lightweight lock
  • When the bias lock exits, Mark Word is not modified, that is, the lock is not released
  • Compared with lightweight locks, biased locks do not need CAS operation when the same thread obtains the lock again, which improves the performance (the lightweight lock obtains the lock every time when it is on the same thread, and the CAS operation must be performed every time when there is no lock)
  • Biased lock only when other threads try to compete for biased lock, the thread holding the biased lock will release the lock, and the thread will not actively release the biased lock
  • The revocation of biased lock is very complex, which has become an obstacle to understanding the code and hindering the reconstruction of synchronous system. Nowadays, it is basically multi-core system, and the disadvantage of biased lock is becoming more and more obvious, so The bias lock is discarded in Java 15

Added by lobobr on Mon, 07 Mar 2022 09:00:23 +0200