(Turn) OopMap (zz) in JVM

Original address: http://www.cnblogs.com/strinkbug/p/6376525.html

When I read Zhou Zhiming's in-depth understanding of JVM virtual machine, I felt that the part about enumerating root nodes/security points was not very clear in the book. After searching for a long time, I felt that this article was written better than the book, although I read it Still very difficult, from a small oopMap data structure can see the essence of hotspot virtual machine garbage collection

The reference type data in the call stack is an important part of the root set of the GC; finding out the references on the stack is an indispensable part of the root enumeration of the GC.

How the JVM chooses to use will affect the implementation of the GC:

If the JVM chooses not to log any data of this type, then it can't tell whether the data at a location in memory should be interpreted as a reference type or an integer type or something else. Under this condition, the implemented GC will be "conservative GC". During GC, the JVM starts to scan the memory from some known location (for example, the JVM stack), and every time it sees a number, it "looks like a pointer to the GC heap". This will involve upper and lower boundary checking (the upper and lower bounds of the GC heap are known), alignment checking (usually there will be alignment requirements when allocating space, if it is 4-byte alignment, then the number that cannot be divisible by 4 is definitely not a pointer. ),some type of. Then recursively scan out like this.

The advantage of conservative GC is that it is relatively simple to implement, and it can be easily used in programming languages ​​that do not have special support for GC to provide automatic memory management. Boehm-Demers-Weiser GC is a typical representative of conservative GC, which can be embedded in programs written in languages ​​such as C or C++.

A little historical story:
Microsoft's JScript and early versions of VBScript also used conservative GC; so did Microsoft's JVM. VBScript later switched back to using reference counting. The descendant of the Microsoft JVM, the CLR in .NET, uses a fully accurate GC instead.
To catch up with the announcement at a conference, Microsoft's original JVM prototype was only a month or so away from being built to reach Java standards. Therefore, we have to use a simple method to implement it first, and naturally choose conservative GC.
Source: Interview with Patrick Dussud on Channel 9, around 23 minutes

The disadvantages of conservative GC are:
1. Some objects should have been dead, but there are suspected pointers pointing to them, so that they escape the collection of GC. This is safe for program semantics, because all objects that should be alive will be alive; but it is not a good thing for memory footprint, there will always be some data that is no longer needed and still occupies GC heap space. The specific implementation can reduce the proportion of such useless objects through some adjustments, which can alleviate (but not cure) the problem of large memory footprint.

2. Since it is not known whether the suspected pointers are really pointers, their values ​​cannot be rewritten; moving the object means correcting the pointer. In other words, the object cannot be moved. There is a way to support the movement of objects while using conservative GC, that is to add a layer of indirection, not directly through the pointer to achieve the reference, but add a layer of "handle" (handle) in the middle, all references first point to In a handle table, find the actual object from the handle table. In this way, if you want to move the object, you only need to modify the contents of the handle table. But in this case, the access speed of the reference is reduced. The Classic VM of Sun JDK has used this full handle design, but the effect is really not good.

Since the JVM needs to support rich reflection functions, it is necessary for the object to understand its own structure, and this information can also be used by the GC, so few JVMs will use a completely conservative GC. unless you're really lazy...

The JVM can choose not to record type information on the stack, but record type information on the object. In this case, the process of scanning the stack will still be the same as the above-mentioned process, but when scanning an object in the GC heap, because the object has enough type information, the JVM can determine where the data in the object is a reference type. . This is "semi-conservative GC", also known as "conservative with respect to the roots".

To support semi-conservative GC, the runtime needs to have enough metadata on the object. In the case of the JVM, these data may be computed in the class loader or in the object model module, but do not require special support from the JIT compiler.

Boehm GC was mentioned earlier, in fact it supports not only a fully conservative way, but also a semi-conservative way. Both GCJ and Mono are examples of using Boehm GC in a semi-conservative way.

An early version of Google Android's Dalvik VM was also an example of using semi-conservative GC. By mid-2009, however, the Dalvik VM internal version had started to support exact GC - at the cost of a ~9% increase in the size of the optimized DEX file.
In fact, many older JVMs opt for this implementation.

Since the data inside the heap is accurate in the semi-conservative GC, it can support the movement of some objects under the condition that the pointer is directly used to realize the reference. The method is to set only the objects that can be directly scanned by the conservative scan as immovable ( pinned), and objects scanned from them can be moved.
Completely conservative GCs usually use algorithms that don't move objects, such as mark-sweep. Semi-conservative GCs can use either mark-sweep or algorithms that move parts of objects, such as Bartlett-style mostly-copying GC.

The semi-conservative GC support for JNI method calls will be easier: whether it is a JNI method call or not, the stack is swept away... it's done. No additional handling of references is required. Of course, the cost is the same as the completely conservative method, and there will be a problem of "suspected pointer".

The opposite of conservative GC is "accurate GC", the original text can be precise GC, exact GC, accurate GC or type accurate GC. Foreigners are also very troublesome. "Accurate" is not unified in one word...
What is "accurate"? The key is "type", that is to say, given a piece of data at a certain location, it is necessary to know what its exact type is, so that the meaning of the data can be interpreted reasonably; the meaning that GC cares about is "this piece of data" Is it a pointer?"
To implement such a GC, the JVM must be able to determine whether the data in all locations is a reference to the GC heap, including the data in the active record (stack + register).

There are several ways:

1. Let the data itself be tagged. This practice is not common in the JVM, but is reflected in some other language implementations. I won't go into details. The marking method is more common in semi-conservative GC. For example, CRuby uses marking semi-conservative GC. CLDC-HI is more interesting. Each slot on the stack is paired with a word-length tag to indicate its type. In this way, the overhead of stack map is reduced; similar implementations have not been seen elsewhere. Not so.
2. Let the compiler generate special scan code for each method. I haven't seen this done in a JVM implementation, although in other language implementations.
3. Record the type information from the outside and save it as a mapping table. There are now three mainstream high-performance JVM implementations, HotSpot, JRockit, and J9. Among them, HotSpot calls such a data structure OopMap, JRockit calls it livemap, and J9 calls it GC map. Apache Harmony's DRLVM also calls it GCMap.
To achieve this function, the interpreter and JIT compiler in the virtual machine need to have corresponding support, so that they can generate enough metadata for the GC.
There are generally two ways to use such a mapping table:
1. Traverse the original mapping table every time, and scan the offsets of the loop one by one; this usage is also called "interpretation";
2. Generate for each mapping table A piece of custom scan code (think of a loop that scans the mapping table being unrolled), and the generated scan code is executed directly each time the mapping table is used; this usage is also called "compiled".

In HotSpot, the object's type information contains its own OopMap, which records what type of data is at what offset in the type of object. So the scan from the object out can be accurate; this data is calculated during the class loading process.

oopMap can be simply understood as debugging information. In the source code, each variable has a type, but the compiled code only has the position of the variable on the stack. oopMap is an additional piece of information that tells you where on the stack was originally what. This information is generated along with the machine code during JIT compilation. Because only the compiler knows the correspondence between the source code and the generated code. Each method may have several oopMaps, that is, according to the safepoint, the code of a method is divided into several segments, each segment of code has an oopMap, and the scope is naturally limited to this segment of code. If multiple objects are referenced in the loop, there will definitely be multiple variables, which will occupy multiple positions on the stack after compilation. The oopMap of this code will contain multiple records.

Each JIT-compiled method will also record an OopMap in some specific locations, recording which locations on the stack and registers are referenced when an instruction of the method is executed. In this way, the GC will query these OopMaps when scanning the stack to know where the references are. These specific positions are mainly:
1. The end of the loop
2. Before the method returns / after the call instruction that calls the method
3. The position where an exception may be thrown

This location is called a "safepoint". The reason for choosing some specific locations to record the OopMap is because if the OopMap is recorded for each instruction (the location), these records will be relatively large, and the space overhead will not be worth it. Selecting some key points to record can effectively reduce the amount of data that needs to be recorded, but still achieve the purpose of distinguishing references. Because of this, the GC in HotSpot can not be entered at any location, but only at the safepoint.
The method still executed in the interpreter can automatically generate an OopMap for the GC through the function in the interpreter.

Usually these OopMaps are compressed and stored in memory; they are decompressed and used on demand during GC.
HotSpot uses OopMap in an "interpreted" way, scanning the corresponding offset each time through the items in the loop variable.

For JNI methods in Java threads, they are neither executed by the interpreter in the JVM nor generated by the JIT compiler of the JVM, so OopMap information will be missing. So how does the GC maintain accuracy when encountering such a stack frame?
HotSpot's solution is: all references that pass through the JNI call boundary (parameters passed in to call JNI methods, return values ​​returned from JNI methods) must be wrapped in "handles". When JNI needs to call the Java API, it must wrap the pointer with the handle itself. In this implementation, the "jobject" written in the JNI method is not actually a pointer to the object directly, but a handle first, through which the object can be accessed indirectly. In this way, when scanning a JNI method, it does not need to scan its stack frame - just scan the handle table to get all the objects in the GC heap that can be accessed from the JNI method.
But this also means that calling the JNI method will have the overhead of wrapping/unwrapping the handle, which is one of the reasons why the call of the JNI method is slow.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325854831&siteId=291194637
JVM
JVM