JVM Learning (2)—A simple understanding of the four major Java references (strong, soft, weak, and virtual)

1. Overview of Java references

There are four types of references in Java to make it more flexibleManage object life cycle, in order to flexibly handle it in different scenariosObject recycling problem. Different types of references are handled differently during garbage collection and can be used to implement different garbage collection strategies. Java currently divides it into four categories. The class diagram is as follows:

Insert image description here

Java technology allows the use of finalize()methods to do the necessary cleanup before the garbage collector clears objects from memory.

    @Deprecated(since="9")
    protected void finalize() throws Throwable {
    
     }
}

The traditional finalize() method has been marked as deprecated in Java 9 and has been deleted in Java 11 (it is recommended to use CleanerJava's new generation memory recycling mechanism). This is because there are many problems with the finalize() method, including but not limited to the following:

  1. The finalize() method is not guaranteed to be called in time, which may cause memory leaks and other problems.
  2. The execution timing of the finalize() method is uncertain, which may lead to unpredictable behavior of the program.
  3. The finalize() method is called by the JVM. Manual calling is error-prone and not flexible enough.

=>The execution process of Cleaner is as follows:

In JDK9, Java provides a new mechanism to replace the traditional finalize() method to clean up objects, which is Cleaner. Cleaner is a Java class used to register objects that need to be cleaned and related cleaning methods. It is used 本地内存技术to clean up Java objects promptly after they are released.

When a Java object is no longer referenced, the memory it occupies is not released immediately, but is marked as "recyclable". The JVM will start a thread in the background to periodically scan these "recyclable" objects and clean up objects that are no longer referenced. Cleaner is the cleaning operation of objects during this scanning process.

When using the Cleaner mechanism, developers need to create a Cleaner object, use this object to register the objects that need to be cleaned, and define a cleaning method. When the registered object is recycled, the JVM will call the registered cleanup method during the background scanning process to clean up the object. Compared with the traditional finalize() method, the cleaning operation of the Cleaner mechanism is more flexible, can be triggered in a variety of situations, and will not be affected by the instability of the finalize() method.

Here is an example using Cleaner:

public class Resource implements AutoCloseable {
    
    
    private static final Cleaner cleaner = Cleaner.create();

    private final Cleaner.Cleanable cleanable;
    private ByteBuffer buffer;

    public Resource(int size) {
    
    
        buffer = ByteBuffer.allocate(size);
        cleanable = cleaner.register(this, new ResourceCleaner(buffer));
    }

    @Override
    public void close() throws Exception {
    
    
        cleanable.clean();
    }

    private static class ResourceCleaner implements Runnable {
    
    
        private ByteBuffer buffer;

        public ResourceCleaner(ByteBuffer buffer) {
    
    
            this.buffer = buffer;
        }

        @Override
        public void run() {
    
    
            System.out.println("Cleaner is cleaning the resource");
            buffer = null;
        }
    }
}

In the above example, the Resource class uses Cleaner to release resources. The buffer object in the Resource class will allocate memory when instantiated. When you are finished using the Resource instance, you need to call the close() method to release the resource. The close() method calls the clean() method of Cleaner.Cleanable to trigger the release of resources. At the same time, the ResourceCleaner class implements the Runnable interface, and the run() method is used to clean resources. In this example, the resource cleanup operation is to set the buffer to null. This way, once the instance is reclaimed by the garbage collector, the clean() method will be automatically called, thereby releasing the resources.

  1. Generally speaking, classes that implement the Closeable or AutoCloseable interface need to manually call the close() method to release resources after use . However, in some cases, the JVM will automatically call the close() method. For example, in the try-with-resources statement, when the try code block is executed, the JVM will automatically call the close() method of the corresponding resource to release the resource without manually transfer. However, if multiple resources are used in the try-with-resources statement, you need to pay attention to their release order and ensure that the resources opened later are closed first to avoid possible resource leaks.
     
  2. ResourceCleaner is automatically triggered by the JVM's garbage collector. When an object is no longer referenced, its clean() method will be automatically called. Specifically, when the garbage collector scans an object, if the object implements the Cleaner.Cleanable interface, the garbage collector will register it in a global, and the ReferenceHandler 队列threads 队列in this will use 定期触发the clean() method of these objects. . This process is automatically performed by the JVM and the programmer does not need to trigger it manually.

2. A brief introduction to strong, soft, weak and virtual references

1. Strong reference (Reference default)

It is the most common reference type and the default reference type. When there is insufficient memory, the JVM starts garbage collection. For strongly referenced objects, even if an OOM occurs, the object will not be recycled and will not be released.

As long as a strong reference still points to an object, it means that the object stillalive, the garbage collector will not touch these objects. The most common thing in Java is to assign an object to a reference variable. This reference variable is a strong reference. When an object is referenced by a strong reference, it is handled 可达状态that it cannot be reclaimed by the garbage collector, even if the object will never be used later, the JVM will not reclaim it. Therefore, strong references are one of the main causes of Java memory leaks.

For example, the following example:

class MyObject {
    
    

    public byte[] buff = new byte[1000 * 1000 * 3];
    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}
public class ReferenceDemo {
    
    

    public static void main(String[] args) {
    
    

        MyObject myObject = new MyObject()System.out.println("gc before myObject = " + myObject);
        try {
    
    
            byte[] bytes = new byte[1000 * 1000 * 8];
            System.gc();
        } finally {
    
    
            System.out.println("gc after myObject = " + myObject);
        }
    }

First, -Xms10m -Xmx10mmodify the JVM heap memory to 10M through commands to facilitate testing, then define a class MyObject to allocate an array occupying 3M of heap memory, and then allocate an 8M array in the main() method. This will definitely add up to more than 10M heap memory. , will cause OOM, but you can manually call System.gc to trigger garbage collection, but it is not recycling here (the finalize() method will definitely not be called), because this is the case, even if 强引用OOM occurs, it will not be recycled.

Before the JVM prepares for garbage collection, it will first call the finalize () method to do some cleanup work, so you only need to override this method to demonstrate the effect (you will not do this at work), and then call it manuallySystem.gc()Let him trigger the finalize () method call.
垃圾回收线程It is a background daemon thread that collects garbage regularly. System.gc() is provided here to facilitate manual triggering of GC garbage collection.

The effect is as follows:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = main.juc.MyObject@5ebec15
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at main.juc.ReferenceDemo.main(ReferenceDemo.java:27)

So how can we let him recycle it? You can use the simplest way to directly assign the strong reference to null. The modified code is as follows:

class MyObject {
    
    

    public byte[] buff = new byte[1000 * 1000 * 1];
    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}
public class ReferenceDemo {
    
    

    public static void main(String[] args) {
    
    

        MyObject myObject = new MyObject();

        System.out.println("gc before myObject = " + myObject);
        try {
    
    
            byte[] bytes = new byte[1000 * 1000 * 7];
            myObject = null;
            System.gc();
            System.out.println(">>>>>>垃圾回收..."+myObject);
        } finally {
    
    

            System.out.println("gc after myObject = " + myObject);
        }
    }
}

The output is as follows:

gc before myObject = main.juc.MyObject@5ebec15
>>>>>>调用 finalize 清理资源...
>>>>>>垃圾回收...null
gc after myObject = null

Process finished with exit code 0

myObject = nullCan help JVM perform garbage collection.

2. Soft reference (SoftReference decorative object)

If an object has only soft references, then in the systemNot enough storage, the garbage collector will tryRecycle the object. This reference type is usually used forcachein so that inAutomatically release cache when memory is insufficient, SoftReferencesoft references can be created through classes. Examples are as follows:

class MyObject {
    
    

    public byte[] buff = new byte[1000 * 1000 * 3];
    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}
public class ReferenceDemo {
    
    

    public static void main(String[] args) {
    
    

        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        try {
    
    

            System.out.println("gc before myObject = " + softReference.get());
            byte[] buf = new byte[1000*1000*3];
            System.gc();
            Thread.sleep(3000);

            System.out.println("gc after myObject = " + softReference.get());
        } finally {
    
    
            System.out.println(">>>>>>内存不够 myObject="+softReference.get());
        }

    }
}

First, use -Xms10m -Xmx10mthe command to modify the JVM heap memory to 10M to facilitate testing, and then SoftReferencepackage the objects that need to be marked as soft references through the keyword. In the MyObject class, an array is first allocated. The array occupies about 3M space in the heap memory. The main() method occupies 3M space, which is far from exceeding 10M. The allocated memory size is then manually called. The effect is as follows:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = main.juc.MyObject@5ebec15
>>>>>>内存不够 myObject=main.juc.MyObject@5ebec15

Obviously, if there is enough memory, GC garbage collection will not be triggered. Now modify the code to the following, the code is as follows:

class MyObject {
    
    

    public byte[] buff = new byte[1000 * 1000 * 3];
    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}
public class ReferenceDemo {
    
    

    public static void main(String[] args) {
    
    

        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        try {
    
    

            System.out.println("gc before myObject = " + softReference.get());
            byte[] buf = new byte[1000*1000*7];
            System.gc();
            Thread.sleep(3000);

            System.out.println("gc after myObject = " + softReference.get());
        } finally {
    
    
            System.out.println(">>>>>>内存不够 myObject="+softReference.get());
        }
    }
}

The effect is as follows:

gc before myObject = main.juc.MyObject@5ebec15
gc after myObject = null
>>>>>>内存不够 myObject=null

Obviously when there is insufficient memory, GC garbage collection will be triggered to release the memory space referenced by SoftReference . This is the benefit of SoftReference reference. If the memory is sufficient, it will not be recycled. Otherwise, it will be recycled.

3. Weak reference (WeakReference decorative object)

If an object has a weak reference, as long as the garbage collector scans the object, the object will be recycled regardless of whether there is sufficient memory. Weak references are often used to implement 缓存memory-sensitive caches, monitors, and other functions. Weak references can be created through the WeakReference class. Use -Xms10m -Xmx10mthe command to modify the JVM heap memory to 10M for testing. code show as below:

Use -Xms10m -Xmx10mthe command to modify the JVM heap memory to 10M for testing.

class MyObject {
    
    

    public byte[] buff = new byte[1000 * 1000 * 3];
    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}
public class ReferenceDemo {
    
    

    public static void main(String[] args) {
    
    
        WeakReference<MyObject> softReference = new WeakReference<>(new MyObject());
        try {
    
    
            System.out.println("gc before myObject = " + softReference.get());
            System.gc();
            Thread.sleep(3000);
            System.out.println("gc after myObject = " + softReference.get());
        } catch (Exception e) {
    
    
            System.out.println(">>>>");
        } finally {
    
    
            System.out.println(">>>>>>内存不够 myObject="+softReference.get());
        }
    }
}

The output is as follows:

gc before myObject = main.juc.MyObject@5ebec15
>>>>>>调用 finalize 清理资源...
gc after myObject = null
>>>>>>内存不够 myObject=null

It can be seen that no matter whether the memory is still there, as long as the GC scans (System.gc()You can start a GC scan) and it will be recycled directly.

Supplement: Let’s 弱引用take a practical case as follows:

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

public class ImageLoader {
    
    
    private Map<String, WeakReference<Image>> cache = new HashMap<>();

    public Image loadImage(String filename) {
    
    
        Image image = null;
        WeakReference<Image> ref = cache.get(filename);

        if (ref != null) {
    
    
            image = ref.get();
        }

        if (image == null) {
    
    
            image = loadImageFromDisk(filename);
            cache.put(filename, new WeakReference<>(image));
        }

        return image;
    }

    private Image loadImageFromDisk(String filename) {
    
    
        // load image from disk
        return null;
    }
}

In this example, the ImageLoader class uses a HashMap to cache the images loaded into memory, with each image corresponding to a weak reference. When a picture needs to be loaded, first check from the cache whether the picture has been loaded into the memory, and if so, return the picture object referenced by the weak reference; otherwise, load the picture from the disk, add it to the cache, and return Picture object.

Since each image object in the cache is a weak reference, when memory is insufficient, the garbage collector will automatically reclaim the memory space occupied by these image objects to achieve memory management.

Supplement: Why do we need to use weak references in ThreadLocal ?

ThreadLocal uses weak references to prevent memory leaks. Since ThreadLocalMap exists in Thread, if strong references are used, once the ThreadLocal object is recycled, its corresponding Entry object in ThreadLocalMap will not be recycled, which will cause the value object in the Entry object not to be recycled for a long time. Eventually leading to memory leaks. Using weak references allows the ThreadLocal object to be recycled, and the value object in the Entry object to be recycled during the next ThreadLocalMap operation, thus avoiding the problem of memory leaks.

4. Virtual reference (PhantomReference decoration object)

Also called a ghost reference, if an object only has a virtual reference, then the object is the same as having no reference and may be recycled by the garbage collector at any time. Virtual references are usually used to track the status of objects being recycled by the garbage collector. Virtual references can be created through the PhantomReference class.

Gu Mingsi means that it is in name only. Unlike other references, virtual references do not determine the declaration cycle of the object.

If an object only holds virtual references, then it is the same as having no references and may be recycled by the garbage collector at any time. It cannot be used alone or access the object through virtual references.Virtual references must be used together with the reference queue ReferenceQueue

The main function of virtual references is to track the status of objects being garbage collected. It just provides a way to ensure that certain operations can be done after the object is finalized().PhantomReference.get()The method always returns null, so the corresponding reference object cannot be accessed. For example: receive a system notification or perform further processing when this object is recycled by the garbage collector. for example:

Use -Xms10m -Xmx10mthe command to modify the JVM heap memory to 10M for testing.

class MyObject {
    
    

    @Override
    protected void finalize() throws Throwable {
    
    
        System.out.println(">>>>>>调用 finalize 清理资源...");
    }
}

public class ReferenceDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        PhantomReference<MyObject> softReference = new PhantomReference<>(new MyObject(), queue);
        List<byte[]> list = new ArrayList<>();
        new Thread(() -> {
    
    
            while (true) {
    
    
                try {
    
    
                    Thread.sleep(500);
                } catch (InterruptedException e) {
    
    
                    throw new RuntimeException(e);
                }
                list.add(new byte[1000 *  1000  * 1]);
                System.out.println("softReference.get() = " + softReference.get());
            }
        }).start();

        new Thread(() -> {
    
    
            while (true) {
    
    
                if (queue.poll() != null) {
    
    
                    System.out.println(">>>>>>引用队列有值啦...");
                }
            }
        }).start();

        Thread.sleep(8000);
    }
}

The output is as follows:

softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
softReference.get() = null
>>>>>>调用 finalize 清理资源...
softReference.get() = null
softReference.get() = null
>>>>>>引用队列有值啦...java.lang.ref.PhantomReference@5c4efc23>>>type=class java.lang.ref.PhantomReference
softReference.get() = null

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "Thread-0"
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated

In the above code, a virtual reference is created through PhantomReference and passed into the ReferenceQueue queue. Thread 1 continuously applies for space in the heap memory, each time 1M in size, because the object wrapped by a virtual reference passessoftReference.get()All return null. Thread 1 continues to be added in this way until the memory is insufficient, and the virtual reference object will be placed in the ReferenceQueue queue. The ReferenceQueue queue can help us be notified in time when the virtual reference object is recycled, so as to perform necessary cleanup work. In fact, it means that this virtual reference wants to leave some last words before death. Human beings can monitor this reference queue to see who has last words before death, and help them realize it.

Replenish:

Phantom Reference is the weakest type of Java reference. When the virtual reference object is recycled by GC, it will be placed in a queue managed by ReferenceQueue .

A common use of virtual references is to manage DirectByteBuffer objects. It allows us to monitor the recycling time of DirectByteBuffer objects. Once the DirectByteBuffer objects are recycled by GC, we can be notified to perform necessary resource release operations, such as releasing memory mapped files, etc.

The following is a simple example code that uses virtual references to manage DirectByteBuffer:

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.nio.ByteBuffer;

public class DirectByteBufferTest {
    
    
    private static final int BUFFER_SIZE = 1024 * 1024;
    private static final int MAX_BUFFERS = 10;
    private static final ByteBuffer[] buffers = new ByteBuffer[MAX_BUFFERS];
    private static final ReferenceQueue<ByteBuffer> queue = new ReferenceQueue<>();

    static {
    
    
        for (int i = 0; i < MAX_BUFFERS; i++) {
    
    
            buffers[i] = ByteBuffer.allocateDirect(BUFFER_SIZE);
            new PhantomReference<>(buffers[i], queue);
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        for (int i = 0; i < MAX_BUFFERS; i++) {
    
    
            ByteBuffer buffer = buffers[i];
            System.out.println("buffer " + i + ": " + buffer);
            buffers[i] = null;
            buffer = null;
        }

        System.gc();
        Thread.sleep(1000);

        Reference<? extends ByteBuffer> ref;
        while ((ref = queue.poll()) != null) {
    
    
            ByteBuffer buffer = ref.get();
            System.out.println("buffer " + buffer + " is released");
            // Release resources here
        }
    }
}

The sample program manages these DirectByteBuffer objects by creating 10 DirectByteBuffer objects and placing their virtual reference objects in a queue . During the running of the program, the addresses of 10 DirectByteBuffer objects will first be printed out, and then all their references will be set to null so that they can be recycled by the GC. The program then calls System.gc() to trigger a garbage collection, and then calls Thread.sleep() to let the program sleep for 1 second to wait for the garbage collection to complete. Finally, the program takes out the virtual references of all recycled DirectByteBuffer objects from the ReferenceQueue and performs necessary resource release operations.

Guess you like

Origin blog.csdn.net/qq_35971258/article/details/129237991