聊聊JVM软引用和弱引用

Posted wen-pan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊JVM软引用和弱引用相关的知识,希望对你有一定的参考价值。

一、说明

由于软引用和弱引用工作流程相同,所以这里只介绍软引用!

  • 软引用和弱引用最大的不同点:

    • 软引用:发生了垃圾回收,并且回收后内存仍然不足,并且【被软引用对象指向的对象没有强引用,那么【被软引用对象指向的对象】就会被回收。
    • 弱引用:只要发生了垃圾回收,并且【被弱引用对象引用的对象】没有被其他【被弱引用对象引用的对象】就会被GC回收
  • JVM中有四种引用,分别是强、软、弱、虚千万不要软弱虚这三种引用理解为栈上的变量指向堆上的对象指针

  • 软引用、弱引用、虚引用:这三种引用其实就是一个个的类,通过对应的类名翻译过来的中文名称。

    // 软引用
    public class SoftReference<T> extends Reference<T> {
    
    }
    
    // 弱引用
    public class WeakReference<T> extends Reference<T> {
      
    }
    
    // 虚引用
    public class PhantomReference<T> extends Reference<T> {
      
    }
    
  • 软引用和弱引用的工作原理是非常类似的,虚引用和前面两种引用出入稍大

  • 软、弱、虚三种引用都可以配合队列来使用,不过软、弱两种引用可以不配合队列单独使用。而虚引用必须配合队列使用

二、软引用原理

  • 发生了垃圾回收,并且回收后内存仍然不足,并且【被软引用指向的对象没有强引用,那么【被软引用指向的对象】就会被回收。

1、示例代码

// 设置JVM运行时参数:-Xmx20m   控制堆大小为20m方便演示
public static void main(String[] args) throws IOException {
    // 创建A对象,在堆上分配10m的空间
    A a = new A();
    // a 对象是被软引用引用的对象
    SoftReference<A> softReference = new SoftReference<>(a);
    System.out.println("被软引用引用的对象:" + softReference.get());

    // 释放a对象的强引用,如果这里不释放强引用,那么下一行再次申请内存的时候就会OOM。因为a对象有强引用无法被回收
    a = null;
    // 继续在堆上申请10m空间,造成内存不够用,演示 被软引用引用的对象被垃圾回收
    A aa = new A();

    System.out.println("被软引用引用的对象:" + softReference.get());
}
public class A {

    public static int _10MB = 10 * 1024 * 1024;

    byte[] bytes;

    public A() {
        // 分配一个10的内存
        bytes = new byte[_10MB];
    }

}

2、代码输出

被软引用引用的对象:org.memory.jvm.t1.reference.A@61bbe9ba
被软引用引用的对象:null

3、原理图

  • 对于a对象,他被a变量强引用,被softReference对象引用。
  • 一旦①处的强引用断开了,那么a对象就没有强引用了。那么当堆空间不足时(经过GC垃圾回收后仍然不足),则可以将a对象占用的空间进行回收。

这里有个问题:a对象虽然被回收了,那么softReference对象并没有被回收,一般来说当被软引用引用的对象被回收后,这个软引用对象也就没有多大的用处了,也应该被回收掉。那么如何回收呢?那就是配合队列一起使用!!!

三、软引用配合队列一起使用

SoftReference的构造函数中就可以看出来,他接收一个队列参数。当【被软引用引用的对象】(a对象)被回收时,JVM会把softReference对象放入ReferenceQueue中。我们可以自己监听这个队列,将队列中的元素取出来,做自定义的操作。

// 接收一个队列
public SoftReference(T referent, ReferenceQueue<? super T> q) {
    super(referent, q);
    this.timestamp = clock;
}

1、代码演示

// 设置JVM运行时参数:-Xmx20m   控制堆大小为20m方便演示
public static void main(String[] args) throws IOException {
    // 创建A对象,在堆上分配10m的空间
    A a = new A();
    // 队列
    ReferenceQueue<A> queue = new ReferenceQueue<>();
    SoftReference<A> softReference = new SoftReference<>(a, queue);
    System.out.println("软引用对象:" + softReference);

    // 释放a对象的强引用,如果这里不释放强引用,那么下一行再次申请内存的时候就会OOM。因为a对象有强引用无法被回收
    a = null;
    // 继续在堆上申请10m空间,造成内存不够用,演示 被软引用引用的对象被垃圾回收
    A aa = new A();

    Reference<? extends A> reference = queue.poll();
    System.out.println("队列中的对象:" + reference);
}

2、结果输出

软引用对象:java.lang.ref.SoftReference@61bbe9ba
队列中的对象:java.lang.ref.SoftReference@61bbe9ba

可以看到这个软引用对象确实被添加到队列中了。

四、软引用和弱引用使用场景

  • 软引用和弱引用一般都可以被用于实现内存敏感的缓存

  • 弱引用的使用体现在threadlocal里

    • ThreadLocalMap中的entry实体就是一个弱引用
    static class ThreadLocalMap {
    
      	// Entry是一个弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
    

以上是关于聊聊JVM软引用和弱引用的主要内容,如果未能解决你的问题,请参考以下文章

Java 如何有效地避免OOM:善于利用软引用和弱引用

JVM 判断对象已死亡?

搞定 JVM 垃圾回收就是这么简单

JVM总结之垃圾回收详解

Java 如何有效地避免OOM:善于利用软引用和弱引用

使用软引用和弱引用防止内存溢出