GCRoot Algorithm Implementation of JVM Garbage Collection Series

sequence

Happy National Day with friends
insert image description here

introduction

This article will introduce in detail how the HotSpot virtual machine implements the root reachability algorithm (GCROOT)

Reference book: "In-depth understanding of Java Virtual Machine"

Personal java knowledge sharing project - gitee address

Personal java knowledge sharing project - github address

Enumerate root nodes

Taking the operation of finding reference chains from GC Roots nodes in reachability analysis as an example, the nodes that can be used as GC Roots are mainly in global references (such as constants or class static attributes) and execution contexts (such as local variable tables in stack frames) ), many applications now only have hundreds of megabytes in the method area. If you want to check the references here one by one, it will inevitably consume a lot of time.

In addition, the sensitivity of reachability analysis to execution time is also reflected in GC pauses, because this analysis must be performed in a snapshot that can ensure consistency - here "consistency" means the entire The execution system looks like it is frozen at a certain point in time, and the object reference relationship is not allowed to change during the analysis process. If this point is not satisfied, the accuracy of the analysis results cannot be guaranteed. This is one of the important reasons why all Java execution threads must be stopped when the GC is in progress (Sun calls this thing "Stop The World"), even in the CMS collector that claims (almost) no pauses, It is also necessary to pause when enumerating the root node.

Since the current mainstream Java virtual machines use accurate GC, when the execution system stops, there is no need to check all the execution contexts and global reference locations without any omissions. The virtual machine should have a way to directly know Where are object references stored. In the implementation of HotSpot, a set of data structures called OopMap is used to achieve this purpose. When the class loading is completed, HotSpot calculates what type of data is at what offset in the object, and compiles it in JIT During the process, which locations in the stack and registers are references are also recorded at specific locations. In this way, the GC can directly know the information when scanning. The following is a local code of the String.hashCode() method generated by the HotSpot Client VM. You can see that the call instruction at 0x026eb7a9 has an OopMap record, which indicates the EBX register and the memory area at offset 16 in the stack. A reference to an ordinary object pointer (Ordinary Object Pointer), the effective range is from the call instruction to 0x026eb730 (the starting position of the instruction stream) + 142 (the offset of the OopMap record) = 0x026eb7be, that is, the hlt instruction.

insert image description here

safe point

With the assistance of OopMap, HotSpot can quickly and accurately complete the enumeration of GC Roots, but a very real problem arises: there are many instructions that may cause changes in the reference relationship, or changes in the content of OopMap, if for each instruction Both generate the corresponding OopMap, which will require a lot of additional space, so that the space cost of GC will become very high.

In fact, HotSpot does not generate an OopMap for each instruction. As mentioned earlier, it only records this information at a "specific location". These locations are called safe points (Safepoint), that is, they are not in all places when the program is executed. can pause to start GC, and only pause when a safe point is reached. The selection of Safepoint can neither be too small so that the GC waits too long, nor can it be too frequent so that the runtime load is excessively increased. Therefore, the selection of safe points is basically based on the standard of "whether the program has the characteristics of allowing the program to execute for a long time"-because the execution time of each instruction is very short, the program is unlikely to be executed due to the length of the instruction stream The most obvious feature of "long execution" is the multiplexing of instruction sequences, such as method calls, loop jumps, exception jumps, etc., so instructions with these functions will generate Safepoint.

For Sefepoint, another issue that needs to be considered is how to make all threads (not including threads executing JNI calls) "run" to the nearest safe point and then stop when GC occurs. There are two options to choose from: Preemptive Suspension and Voluntary Suspension. Preemptive Suspension does not require the active cooperation of thread execution code. When GC occurs, all threads are first interrupted. , if it is found that the place where the thread is interrupted is not at the safe point, just resume the thread and let it "run" to the safe point. There are almost no virtual machine implementations that use preemptive interrupts to suspend threads in response to GC events.

The idea of ​​active interruption is that when the GC needs to interrupt the thread, it does not directly operate on the thread, but simply sets a flag, and each thread actively polls this flag when executing, and when the interrupt flag is found to be true, it interrupts and suspends itself . The place where the polling flag coincides with the safe point, plus the place where memory needs to be allocated to create an object. The test command in the following code is a polling command generated by HotSpot. When the thread needs to be suspended, the virtual machine sets the memory page at 0x160100 as unreadable. When the thread executes the test command, it will generate a self-trap exception signal. The thread is suspended in the exception handler to implement waiting, so that one assembly instruction completes safe point polling and triggers thread interruption.

insert image description here

safe area

Using Safepoint seems to have perfectly solved the problem of how to enter the GC, but the actual situation is not necessarily the case. The Safepoint mechanism ensures that when the program is executed, it will encounter a Safepoint that can enter the GC within a short period of time. However, what about when the program is "not executing"? The so-called non-executing of the program means that no CPU time is allocated. A typical example is that the thread is in the Sleep state or Blocked state. At this time, the thread cannot respond to the interrupt request of the JVM and "walks" to a safe Where the interrupt is pending, the JVM is obviously less likely to wait for the thread to be reassigned CPU time. In this case, a safe region (Safe Region) is needed to solve it.

A safe area means that within a piece of code, the reference relationship will not change. It is safe to start GC anywhere in this region. We can also regard Safe Region as an extended Safepoint.

When the thread executes the code in the Safe Region, it first marks that it has entered the Safe Region, so that when the JVM initiates GC during this period, it does not need to worry about the thread that marks itself as the Safe Region state. When the thread is about to leave the Safe Region, it checks whether the system has completed the enumeration of the root node (or the entire GC process). If it is completed, the thread continues to execute, otherwise it must wait until it receives the safe to leave Safe Region Region's signal.

The implementation of the GCRoot algorithm in this article is excerpted from the book "In-depth understanding of the java virtual machine"

Guess you like

Origin blog.csdn.net/a_ittle_pan/article/details/127195130