On the Internet to see a pile of data are only clouds in the fog, only about the process, the specific implementation principle is not clear enough. Later, FOLLOWING the guidance of RednaxelaFX, I read several relevant original English papers and suddenly became enlightened, so I wrote this summary.

Mark word

First of all, the space in the Java object header is divided into two parts. One part is used to store Pointers to the corresponding Class data in the metadata space, and the other part is used to store hashcode, synchronized, GC and other related information, which is called mark Word.

Figure 3.1 shows the memory state of Mark Word in each stage, in which two bits are fixed to mark the state of the object in which there is no lock, biased lock, light lock, weight lock, etc. Next, related concepts of synchronized lock will be analyzed mainly according to this figure.

Thin Lock

Light locks are also known as stack locks. When a thread requests a light lock, it copies the Mark Word information into a structure called Lock Records in the current stack memory. The CAS changes the lock flag bit to 00 (light lock status). And save a pointer to the lock record in the Product header reference in Mark Word. The thread determines whether it owns the lock by determining whether the pointer in the Mark Word points to the Lock Record in its stack.

The Lock Record contains two parts, one is the object header information copied from the Mark Word, and the other is a pointer to the locked object.

Fat lock

Objects that do not compete for a lock in a heavyweight lock are suspended by Park, and unpark wakes up subsequent threads when exiting the synchronized block. Wake up operations have additional overhead associated with operating system scheduling. The lock structure held is an ObjectMonitor object that contains a synchronization queue (_CXQ and _EntryList) and a wait queue (_WaitSet)

  • The queue to be added according to the policy when the notify or notifyAll wakes up (policy defaults to 0)
  • Wake up the next thread according to QMode policy when exiting synchronized block (QMode defaults to 0)

Ps: Weight lock implementation principle is not involved in this paper, so I do not do in-depth study, simple summary, later have time to make up haha.

Inflation of lock

When the lightweight lock acquisition fails (CAS fail), the thread attempts to inflate the lock to a weight lock. In order to ensure that mark Word will not be changed by other threads in the process of inflation, CAS first sets Mark Word to NULL(0), which is the intermediate state of inflating in Figure 3.1. At this time, the thread holding the lock cannot release the lock and waits for the completion of lock expansion together with other threads. During the expansion cycle, the following lock states will be judged and treated differently:

  • Weight lock expansion completed (lock marker 10)

    Another thread has finished ballooning the lock and exits the ballooning loop

  • Lock ballooning (Mark word Null)

    Another thread is already working on lock expansion, and wait until lock expansion completes.

  • The lock status is light lock (lock flag 00)

    Allocate ObjectMonitor objects and use CAS to inflating the Mark Word. If the CAS fails, release ObjectMonitor objects and retry the inflating cycle. If CAS is successful, enable the Monitor object, copy the mark Word value into ObjectMonitor, and populate the Mark Word with a reference to the ObejctMonitor object.

  • No lock state (lock flag 01)

    Assign the ObejctMonitor object, try to assign a reference to the Monitor object to Mark Word via CAS, and if that fails release the Monitor and retry the swell loop, otherwise the swell lock successfully exits the loop.

Weight lock inflation is shown in Figure 3.4, where the JVM releases idle Monitor objects during STW, bringing uncontending locks back to a light or biased lock state. This lock degradation is safe because there is no thread to manipulate the lock during STW.

Biased lock

In unlocked and biased scenarios shown in Figure 3.1, the third and last bit is used to control the biased locking switch. If the bit is 0, the biased locking function is disabled, and if it is turned on, there is also a biased locking switch bit stored in the corresponding class of the metspatial object. Used to control whether bias locking can be enabled on a global object. In addition, due to low performance during the first 4s of JVM startup, biased locking is also disabled by default for objects created during this period, and biased locking is normally enabled for objects created after 4s.

Global Safe Points

Safe Point is a term we use a lot in GC, simply because it represents a state in which all threads are suspended. For Biased locks, to unlock bias, you need precise information about the thread state and the thread that acquired the Lock, so you need to wait for all threads to enter the global safety point, which is called STW.

Bulk Rebias/Revoke

When only one thread enters the block repeatedly, the performance cost of biased locking is negligible, but when another thread attempts to acquire the lock, it is necessary to wait until safe Point to revoke the biased lock to lock free state or upgrade to lightweight/heavyweight lock. This process is costly, so if the runtime scenario itself has multithreaded contention, biased locking will not improve performance, but will degrade it. As a result, a batch rebias/undo mechanism has been added to the JVM.

Maintains a bias lock undo counter for each class, on a class basis. Each time a bias undo occurs on an object of this class, the counter +1. When this value reaches the bias threshold (20 by default), the JVM considers the class’s bias lock to be problematic and performs batch bias. When the value reaches the batch undo threshold (40 by default), the batch undo is performed. The bit position of the bias lock switch in the class is 0, and the bias lock function of the class object is disabled.

Principle of batch heavy bias
  1. First, we introduce a concept called epoch, which is essentially a timestamp that represents the validity of a biased lock. The epoch is stored in a MarkWord for a biased object. In addition to the epoch in the object, an epoch value is stored in the class information of the object.
  2. When a global safety point is encountered, such as batch rebias for class C, the epoch stored in class C will be added first to create a new EPOch_new
  3. Then, all thread stacks holding class C instances are scanned to determine whether the thread has locked the object. Only the value of EPOch_new is assigned to the locked object. That is, the value of epoch_new is assigned to the object that is still used.
  4. When a thread attempts to acquire a biased lock, it checks whether the epoch stored in class C is equal to the epoch stored in the target object. If the epoch is not equal to the epoch stored in the target object, Epoch_new = epoch_new (epoch_new); epoch_new = epoch_new (epoch_new); epoch_new = epoch_new (epoch_new); The contending thread can then attempt to re-bias the object.
Request biased lock

When a bias lock is requested, the following steps are performed:

  • Determines the bias lock switch bit of the object

    If the value is 0, it indicates that there is no bias and the lightweight lock request process is directly entered.

  • Determines the bits in the class of a meta-space object

    If the bias lock bit of the class is 0, bias is not allowed and the bias lock bit of the object is set to 0, using lightweight locking instead.

  • Judge epoch

    Check that the epoch of the object is compared with the epoch of the class. If the epoch is not equal, it means that the current bias lock has expired. At the same time, rebias is allowed, and the CAS can directly modify the mark word to point to the current thread.

  • Verify lock ownership

    Check whether the thread ID in Mark Word is the same as the current ID. If they do not match, assume that the current id is in anonymously biased state. Then try to obtain the lock through CAS.

As shown in Figure 3.5, when the request is biased towards the Lock, the reference of the current thread will be assigned to the Mark Word through CAS. Meanwhile, there will be a Lock record in the Lock records list, which reserves the unused Mark Word storage space and the reference to the locked object. This Lock record is used to record the number of Lock reentries (the number of Lock records represents the number of reentries in favor of the Lock), as well as for possible later expansions to light locks. At Safe Point, the JVM directly changes the biased lock that causes lock contention to look like a lightweight lock was used in the first place.

Because of bias locking, the current thread ID is assigned to the mark word location overlapping with the Hashcode location. Therefore, an object that has calculated hashcode cannot use a biased lock. Similarly, an object that is in a biased lock will be revoked and inflated to a weight lock if hashcode calculation is called.

The various states of Mark Word and their transitions are shown in Figure 3.6:

Bias lock problem

In high-concurrency applications, biased locking does not lead to performance gains, but rather to unnecessary threads entering Safepoint or stopping the world due to biased lock cancellation. Therefore, you are advised to disable: -xx: -usebiasedlocking

At the same time, after Java 15, officials have decided to scrap the biased lock design, you can check out JEPS374 for specific reasons.

reference

  • How is biased locking implemented in Java? R big directions zhihu answered
  • Evaluating and improving biased locking in the HotSpot Virtual Machine