A lot of the literature I read about GC Root doesn’t say that the object referenced on the operand stack of the stack frame is also GC Root, including when I went through the book Understanding the Java Virtual Machine in Depth. That’s why I’m curious.

Why do I think the object referenced on the operand stack should also be the GC Root node?

Suppose in the garbage collection flag phase, if the user thread new an object in the method, when the new bytecode instruction is executed, the reference of the new object is saved at the top of the operand stack, and is not saved back to the local variable table, and is not necessarily saved back to the local variable table. Wouldn’t this new new object be recycled?

New an object is not necessarily saved back to the local variable table, such as

public class Main {

    public void hello(a){
        System.out.println("wujiuye");
    }

    public static void main(String[] args) {
        newMain().hello(); }}Copy the code

The created object Main is not stored in the local variable table. The bytecode is as follows:

With questions in mind, I looked through the relevant information about GARBAGE collection and then remembered Safepoint. If the safety point is before or after method execution, there is no need to use the object referenced by the operand stack as the GC Root.

About Safepoint:

1. The suspended thread is in a safe zone, such as WAIT or BLOCK state. Only threads in the running state need to wait for execution to reach a safe point. 2. The method call instruction is the safe point, that is, before the method being called is executed. Method end exits are safe points, such as before the ruturn instruction. 3. The body of the loop is a safe point before entering the next one. Because many loops are time-consuming operations, such as a million loops, the JVM waits for multiple threads to complete the loop before garbage collection, and the process is in suspended animation.

The third point is the end of the loop body. You can also use the loop body as a method before entering the next time. No matter what the method does, the method will never end without creating an object that is not saved back to the local method table. Therefore, there is no need to consider the object referenced by the operand stack as the GC Root, proving me wrong.

In JIT execution, the JIT compiles the Safepoint check code directly into the generated native code. When the JVM needs to let a Java thread enter Safepoint, it simply sets a flag bit and has the Java thread actively check this flag bit when it runs to Safepoint. If the flag is set, the thread pauses, and if not, the execution continues. For example, HotSpot generates a test assembler instruction for polling Safepoint on x86.

Using the JITWatch tool, you can verify. JITWatch also provides a demo for testing savepoints :SafePointTest. So I don’t even have to write the demo. For more information on the JITWatch, see my previous article “JITWatch Looks at assembly code after bytecode has been JIT compiled.”

As you can see, when the incCounter method is decompiled into assembly code, a test instruction is inserted before the return instruction retq, also at the return bytecode instruction.

Having said that, let’s also examine the checks that the body of the loop inserts into Safepoint before each entry into the next loop.

As you can see from the figure, the corresponding bytecode is where the GOto instruction is, that is, at the end of each loop, it is determined whether to jump to execute the TEXT instruction (assembly).

The other thing that can be extended here is escape analysis. It’s also for performance. The JIT just-in-time compiler compiles bytecode that has been executed multiple times into machine code, and also analyzes object creation within the method body. If the object created in the method body does not escape from the method body, that is, it is not referenced elsewhere and is not used by another thread, then the object does not need to be allocated to the heap, but directly to the virtual machine stack. When an object is allocated on the virtual machine stack, the life cycle of the object is that the object is created until the end of the execution of the method and dies when the stack frame is off the stack.

Remember the Jvm tuning parameter -xss, which is used to configure the size of the virtual machine stack. The default size is 1 MB, and the minimum size is 228KB on Linux 64 bit operating systems. Generally, the minimum size is 256KB. Setting this value takes into account the depth of method calls on the thread, as well as the depth of the operand stack and the length of the local variable table for each method on the method call stack. If there is a possibility that the object will be allocated on the stack due to escape analysis, you also need to size this estimate into the stack.

The code snippet in the figure, which I took from an asynchronous framework I wrote earlier, uses ASM to generate bytecode that sets the depth of the operand stack and the length of the local variable table by calling the visitMaxs Method of the Method Visitor. That is, the depth of the operand stack and the length of the local variable table are determined after the Java code is encoded into bytecode.

When you were learning garbage collection, did you ever wonder why you should stop all user threads during GC collection?

If you leave it at the surface, you need to sort out memory after GC collection to solve memory fragmentation, such as Parallel Old’s Old collection, using the flag-tidy algorithm. Collation means that the address of the object changes, so the reference to the object needs to be corrected after GC. Another example is that the object enters the old age from the new generation.

CMS concurrent flags also have STW, in the initial flag and re-flag phases. Because the marked object has the possibility of being referenced by the user thread, the re-marked object has to be re-referenced. It just lowers the total STW time. To further understand the card table, write barriers.