JVM runtime data area exploration and direct memory usage

Without further ado, let’s start with the diagram (<JDK1.8):


It can be seen from the figure that the runtime data area of ​​the JVM can be roughly divided into two parts: data and instructions. The instructions are essentially data, but most of the data is related to the instructions. The three parts on the right are all private to the thread. The counter stores the address of the bytecode instruction executed by the current thread, but this is limited to java methods. If it is a native method, the counter is null. (You can use the javap -v class file name on the command line to view the bytecode), and in the virtual machine stack, a java method corresponds to a stack frame, each stack frame stores the method's local variable table and operand stack and Dynamic linking, etc., so the process of thread execution is equivalent to the process of popping the stack frame. In the process of method execution, how does the data work in the stack frame? Here is a simple example:

public  void  add(int i){
        int a=0;
        int b=1;
        a = b+i;
        
    }

Before running this method, the local variable table first stores the variables i, a, b (positions are 0, 1, 2), and then moves from the variable table to the operand stack when the relevant command is executed. The data is popped from the operand stack and the operation is performed, and the result is pushed onto the operand stack. The specific bytecode is as follows:

   stack=2, locals=3, args_size=1
         0: iconst_0 //Push integer 0 into the operand stack
         1: istore_1
         2: iconst_1 //Push integer 1 into the operand stack
         3: istore_2 // Put the contents of the top of the stack into the slot with index 2 in the local variable table
         4: iload_2 // Load the variable value (b) stored in the slot with index 2 of the local variable table to the operand stack
         5: iload_0 // Load the variable value (i) stored in the slot with index 0 of the local variable table to the operand stack
         6: iadd //The two numbers at the top of the stack are added after being popped out of the stack, and the result is pushed into the stack
         7: istore_1
         8: return //Interested students can go to find the description of the relevant instructions

The native method stack works similarly to the virtual machine stack. The method area is also known as the logical part of the heap (JDK1.8 removed it), it mainly stores static variables and loaded class information and some compiled code and so on. Heap space is the part we are more concerned about, and it is also the main part of GC work. It mainly stores instance objects, array objects and constant pools.

        Another part of the memory is direct memory, but it is not part of the JVM runtime data area, so it is not within the execution scope of the GC collector. Let's track the creation and recycling process of this part of the memory. Because the author has participated in the development of jni before, I know that the created statement is as follows:

ByteBuffer  a = ByteBuffer.allocate(1024);

The reason why DirectByteBuffer cannot be used directly is that all its constructors have no prefix, that is, the access permission is default. Here we review the access modifier of java (upper triangular matrix):

  Access rights class package subclass other package

   public 1 1 1 1 (available to anyone)

   protect 1 1 1 0 (inherited classes can access and have the same permissions as private)

   default 1 1 0 0 (package access rights, that is, can be accessed in the entire package)

   private 1 0 0 0 (elements that cannot be accessed by anyone other than the type creator and the type's internal methods)

Only objects of this class and other classes in the same package can access it. The following is its constructor, of course, there are multiple constructors, and the principles are similar:

DirectByteBuffer(int cap) {                   // package-private
        super (-1, 0, head, head);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);// Allocate off-heap memory (implemented by C's malloc) and return the address of off-heap memory
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); //Build a Cleaner object for it to track the garbage collection of DirectByteBuffer objects
        att = null;

    }

The declaration of the Cleaner object is as follows:

public class Cleaner extends PhantomReference<Object>

Therefore, it can be determined that the virtual reference method is used when reclaiming the memory outside the heap. We know that the virtual reference is the same as the name description. Once the GC encounters it, it will be recycled and added to the ReferenceQueue, which is usually used to track the GC recycling. process. When constructing this Cleaner object, a Deallocator object is also introduced, its implementation is a thread Runnable object, and its run method is as follows:

  public void run() {
            if (address == 0) {
                // Paranoia
                return;
            }
            unsafe.freeMemory(address); //Use the native method to release the memory pointed to by the address
            address = 0;
            Bits.unreserveMemory(size, capacity);//Save the size of the total allocated memory (allocated by page) and the size of the actual memory in the system.
        }

There is a clean method in the method of the Cleaner class:

        if (remove(this)) {
            try {
                this.thunk.run(); //This thunk is the incoming Deallocator object
            } catch (final Throwable var2) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        if (System.err != null) {
                            (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                        }

                        System.exit(1);
                        return null;
                    }
                });
            }

In summary, it can be concluded that direct memory is created by local methods, referenced by GC reclaimed instances, and then released by Cleaner's call to the clean method. The next part will introduce the data area difference between JDK1.8 and JDK1.7 and the GC recovery algorithm


Guess you like

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