Analysis of Object Lock Implementation in Virtual Machine

I. Introduction

Thread synchronization problems are often encountered in the programming process. There are many solutions to synchronization problems in Java (synchronized, JUC, atomic operations, volatile, condition variables, etc.), among which synchronized is the most convenient, easy to use, and is also the most common method in java programming. The most used critical section protection scheme. This article mainly describes the relevant knowledge of object locks, and introduces in detail the implementation principles of the virtual machine for the key methods of synchronized and Object.

2. How to use Java object lock

2.1 Synchronization of instance methods

d1d01f87728ad1735bb712b9907ff4af.png

synchronized Modifies the instance method, the synchronization only works on the method of the current object, and only one thread can enter this method of the object at the same time. There is no mutual exclusion protection for this function of different objects.

2.2 Synchronization of static methods

ef4342141d7934a83ba6dfd87b6df77a.png

synchronized Modifies the static method, the synchronization works on the method of the current class object, and only one thread can enter the method at the same time.

2.3 Synchronization of code blocks

66e05b57c9514e23867dc73650bbed7a.png

In most cases, it is not necessary to protect the entire method. When synchronized modifies a code block, the access of the code block depends on the exclusive access of the object object lock, and only one thread holds the object object lock at the same time.

More precisely, the synchronized keyword is effective depending on the object lock. The monitor-enter obj instruction is generated at the beginning of each synchronized synchronization block, and the monitor-exit obj instruction is generated at the end of the synchronization block, where obj is An object used to control exclusive access. Only one thread can hold the object lock of obj at a time. In 2.1, synchronized relies on real column objects, in 2.2, synchronized relies on class objects, and in 2.3, synchronized relies on object objects.

When an object controls multiple code blocks, multiple code blocks are also mutually exclusive access, such as the following code:

a00feb728805163c754b21d85fe39c89.png

Although code block ① and code block ② are in the two functions, the objects that synchronized depends on are both objects, and these two code blocks are also mutually exclusive access.

2.4 How to use Object wait() and notify()

Object, as the base class of all classes, implements the object method. Typical usage is as follows:

b5e9a2c2ffc6d65d850318b4ab3d2827.png

After thread 1 holds the object object lock and calls the object.wait() method, the thread enters the WAITING state, releases the object object lock, and waits for other threads to wake it up.

When thread 2 holds the object object lock and calls the object.notify() method, wake up thread 1, thread 1

Reacquire the object object lock to continue execution. Object class method description:

954e562836002bf4156e61b5984f5643.png

3. Android object memory structure

3.1 Object memory structure

cc6e3f61b546d835864543ae83a3c819.png

The instance object memory of a class is mainly composed of 3 parts:

1). Object header: The object header includes two fields kclass_ and monitor_, where kclass_ stores a pointer to a class object, through which the corresponding class of the object can be found, and monitor_ is used to store the identification data of the object at runtime, for example : GC flag, hash code, lock status and other information, which will be analyzed in detail later.

2). Instance data, this part stores instance variable values, parent class instance variable values ​​come first, subclasses follow, and instance variable values ​​are sorted in the following order:

34a01576e10f76483f392420f0cbb4e4.png

3). Alignment padding, the object is aligned according to 8byte in memory, if the instance data part is not aligned according to 8byte, the padding is 8byte aligned.

3.2 monitor_ field analysis

The monitor_ field is defined in art/runtime/mirror/object.h, the type is uint32_t, and it mainly has the following three operation functions.

a8f87fe4a2cb32fc310141df49ca18d3.png

The input parameters of the SetLockWord and CasLockWord functions or the return value of the GetLockWord function in the operation function all contain the LockWord variable, and the operation on the monitor_ field is performed through the value of LockWord.

Let's look at the definition of LockWord again:

The definition of the LockWord class is in the art/runtime/lock_word.h file. From the comments, we can see that there are mainly 4 states for the use of LockWord, as follows:

f04f08b0d796cd3a636cd5b17737d1f2.png

The design of LockWord is very delicate, each bit of a 32-bit data is fully utilized, and different states are well distinguished. Each state is described in detail below:

  1. In the unlocked/thin state, the 31-30 bits are 00, and the default state is the unlocked state. When the object performs thread synchronization, it becomes a thin lock state. 27-16 bits record the number of thin lock reentries, and 15-0 bits record the holding time. There is the thread ID of the thin lock.

  2. The 31-30 bits are 01 in the fat lock state. When the object is locked in the thin lock state and there is a new (non-owner) thread competing with it, after a proper waiting period (sched_yield call, cyclic acquisition of the thin lock state), it still cannot be obtained If the object is locked, it will be converted to the fat lock state, and a Monitor resource will be allocated for the object.

  3. In the state of hash state, 31-30 bits are 10, and 27-0 bits store the hash code of the object. In other modes, the hash code will be stored in the Monitor object associated with the object.

  4. In forwarding address state state, 31-30 bits are 11. In the copy phase of concurrent copying GC, when an object is copied, it points to the copied object address. When the thread accesses the object, it accesses the new one through the forwarding address. object.

The 29th bit is the mark bit, which can quickly determine whether it has been marked to avoid repeated marking.

The 28th bit is the read barrier bit. If the bit of the object LockWorkd is set, it will enter the slow path when accessing the members of the object, and judge whether the object needs to be updated. If it needs to be updated, the copied object address will be returned.

Fourth, object lock code analysis

4.1 First, let's look at a piece of code

f6e99c99bab90d7a0c9f30104f652f15.png

This code is relatively simple, mainly has the following two core points:

1). During the execution of the main thread, the obj object is used for thread synchronization, and the obj.wait() function is called, so that the thread is blocked on the obj object lock and waits for wake-up.

2). An anonymous thread is created in the main function. The thread first sleeps for 2000ms, and then wakes up the thread blocked on the obj object lock.

4.2 Compile TestDemo.java, the command is as follows:

f90b5b4567f6fec132a24306bde5884e.png

1).Javac compiles the TestDemo.java file into a TestDemo*.class file, and each class generates a class file during the java compilation process.

2). The .d8 command generates the corresponding dex (Dalvik Executable file) format file after compiling, refactoring, rearranging, compressing, and obfuscating the TestDemo*.class file.

3). The .dexdump.exe command can view the detailed information of the dex file format, such as verification information, dex header information, CFG information for generating dex, dex disassembly information, etc. The detailed usage method can be viewed through the dexdump.exe –help command

View disassembly via dexdump.exe –d classes.dex

The run method instruction information is as follows:

abd7ca36d78bc2f800a90f7519fd6e08.png

The instruction information of the main function is as follows:

dec64ca23a1dc6a41127fad88a629dc8.png

Some instructions are parsed as follows:

86d460240f73e18405a853454dd547db.png

For the detailed format of dex instructions, you can read Google's official documentation:

https://source.android.com/docs/core/runtime/dalvik-bytecode

This article focuses on the detailed implementation of monitor-enter, monitor-exit, Object.wait(), and Object.notify() in the virtual machine.

4.3. Object.wait() process analysis

The calling relationship of Object.wait() is as follows:

655d2977d5b306f699254a7a11180135.png

The Object class is the parent class of all classes. The public wait() method can be called in any class, and finally the wait static method of the monitor.cc file of the virtual machine is called.

02eec4a45b3c0153d73f6448d419584b.png

First construct a Handle object h_obj that operates obj, and notify the jvmti debugging system that a JVMTI_EVENT_MONITOR_WAIT event has occurred through the ObjectWaitStart function.

JVMTI (JVM Tool Interface) is a native programming interface provided by the Java virtual machine, which can be used to develop and monitor the virtual machine, view the internal state of the JVM, and control the execution of the JVM application. The functions that can be realized include but are not limited to: debugging, monitoring, thread analysis, coverage analysis tools, etc.

1a5a23f89ade94d84880be570a1ecfc6.png

First obtain the LockWord field of the h_obj object, and the lock_word.GetState() function obtains the current lock state, mainly in the following situations:

1).hash or unlocked state:

Because calling the wait() method must hold the object lock, these two states will not appear, and if so, an IllegalMonitorStateException will be thrown.

2). Thin lock status:

When the thread holding the object lock is not the thread to wait, an IllegalMonitorStateException is also thrown. When the thread holding the lock is the same as the thread to wait, it is necessary to inflate the thin lock into a fat lock. The process of inflate is in monitor- Analyze in the analysis of the enter command.

When the object lock inflate is in the fat lock state, call the instance method Wait of the Monitor object to let the thread enter the sleep state and wait.

4.4 Object.notify() process analysis

98f27a4e7581c56aeffe8134f5eacbb5.png

Here we directly analyze the DoNotify function:

fc8a81f8bec0801189144e838a92ae25.png

Obtain the lock state of the current obj object through lock_word.GetState(), mainly in the following situations:

1) hash or unlocked status:

An IllegalMonitorStateException is thrown.

2). Thin lock status:

When the thread holding the object lock is not the thread to be notified, an IllegalMonitorStateException is also thrown. When the thread holding the lock is the same as the thread to be notified, it means that there is no thread that needs to be notified and woken up, and returns directly.

3). Fat lock state:

In the Object.notify() process, if the parameter notify_all is false, then directly call mon->Notify(self); notify to wake up the waiting thread.

4.5 monitor-enter process analysis

For interpreted execution and machine code execution modes, the MonitorEnter function of the Object object in the art/runtime/mirror/object-inl.h file will be called eventually.

3de767f89a3357d8dd538bbddcf84a6e.png

Let's analyze the static method MonitorEnter function of the Monitor class.

4d58ff49e23593a9b0ef32d7341a9a16.png

FakeLock is mainly used for thread safety checks, mainly at compile time.

kExtraSpinIters defines when the object lock is held by other threads and is a thin lock, the number of times that competing threads acquire the lock in a loop.

279b5d109bf29453c2ac29710cfe5497.png

Obtain the lock state through lock_word.GetState(). When the lock state is unlocked state, convert to thin lock state, and update the lock count through cas operation.

e77e070819fe8205a432955b3e4d3952.png

When the lock state is thin lock state, the owner thread id of the lock is acquired first. If the owner id is consistent with the competing thread id, there are two situations as follows:

  1. If the lock count plus 1 is less than or equal to (1<<12)-1(4095), update the lock count with lock count+1.

  2. If the lock count plus 1 is greater than (1<<12)-1 (the lock count area cannot be stored), call the InflateThinLocked function to inflate the thin lock.

Atrace*-related functions are mainly used to print systrace-related information, and trylock is false here.

2818a4198396b297ffd5bacb53e83106.png

When the lock state is thin lock state and the lock owner thread id is inconsistent with the competing thread id, wait for a certain amount of time.

The value of runtime->GetMaxSpinsBeforeThinLockInflation() is 50, that is to say, after executing 100 loops to judge the lock status, and then executing 50 times of sched_yield(), the lock resource has not been obtained. If the lock has not been obtained, the lock To expand. sched_yield() will actively give up the execution permission of the current thread and resume execution after a certain time.

b370d9e0b28b7baee40bb555b42adf56.png

When the lock state is fat lock state, obtain the Monitor object through lock_word.FatLockMonitor(); and let the thread enter the waiting state through the Lock function of the Monitor object.

d61f1373f9fc637e6da9f8b8ef8a8e45.png

When the lock state is already in the hash state, directly expand the lock.

Let's look at the process of lock expansion:

b093119e534b2fe432f49e8593a309d1.png

There are two cases of thin lock expansion:

1). The value of the lock count exceeds 4095. At this time, the owner of the lock is the current thread, that is, directly expanded through the Inflate function

2). The owner of the lock is not the current thread. Suspend the owner thread of the lock through SuspendThreadByThreadId (mainly, both the owner thread and the lock expansion thread need to access the LockWord of the object to avoid race conditions), and then inflate through Inflate. After the expansion is completed, wake up the owner thread of the lock.

Look at the process of Inflate:

93facda60aef310488f759d192a4e9ef.png

Obtain a Monitor object m through the MonitorPool::CreateMonitor function, and update the LockWord field of the object through the m->install(self) function. At this time, the LockWord field information includes fat lock status, GC status, and MonitorId, and then save m in monitor_list_ middle.

monitor_list_ stores all Monitor objects used by the current virtual machine. In the process of GC, the objects that Monitor depends on are accessed through the linked list. If the object becomes a garbage object, recycle the Monitor, otherwise update the object information that the Monitor depends on.

MonitorId is used to uniquely identify a Monitor. For the generated method, see the implementation in monitor_pool.h.

Look at the process of Monitor::Lock again:

The implementation of this function is relatively long, and debugging-related code is omitted.

8a5bf1a8217f65c4f46fdfa9240a93b8.png

First, monitor_lock_, the most important member of Monitor, is introduced. It is an instance of Mutex, through which the core logic related to locks is realized.

The TryLock function mainly implements a certain spin wait through the function of Mutex, and sets the state of the lock as the state held by the thread.

monitor_lock_.ExclusiveLock(self); In the ExclusiveLock function of Mutex, the thread blocking is realized through the futex system call, and the futex calling code is as follows.

7ea5d09df0e69ac39c9a395d0192f047.png

4.6 monitor-exit process analysis

Both interpreted execution and machine code execution modes will call the MonitorExit function.

c52803ea13bf86226002a92faa47bc7a.png

Obtain the LockWord status through lock_word.GetState(), and when the status is hash or unlocked, an exception is thrown through the FailedUnlock function.

2c70957059d037ae6c103d213fb27176.png

When the state of LockWord is thin lock, there are two situations as follows:

1). If the owner of the lock is inconsistent with the current thread, an error will be thrown and an exception will be thrown.

2). The owner of the lock is the same thread as the current thread. When the lock is reentrant, set the lock count -1, otherwise set it to the unlocked state.

d818235f26b579dc22396a0711f22a75.png

When the status of LockWord is fat lock, get the Monitor object associated with the object and call the Unlock function

311c4662ad9f501bb8011756996892eb.png

In the Unlock function, lock_count is 0, indicating that the thread is no longer holding the lock.

SignalWaiterAndReleaseMonitorLock wakes up the thread blocked on the lock.

V. Summary

This article briefly explains the use of object locks, the structure of objects in memory, and analyzes the key member LockWord in the object header. Finally, it introduces the functions of synchronized, Object.wait() and Object.notify() in virtual machines. implementation process.

references:

In-depth understanding of Android Java virtual machine ART Deng Fanping

In-depth understanding of java virtual machine Zhou Zhiming

In-depth java virtual machine bill venners

Android T source code

acdee7079ac137a87a8e22c7232dbf54.gif

Long press to follow Kernel Craftsman WeChat

Linux Kernel Black Technology | Technical Articles | Featured Tutorials

Guess you like

Origin blog.csdn.net/feelabclihu/article/details/129359071
Recommended