Java Reference--1

GC与Reference

在JVM进行GC的时候,JVM首先需要判断一个对象是否可以被回收。JVM从GC ROOT的对象开始向下搜索,搜索所走过的路径称为引用链(reference chain),当一个对象没有任何引用时,就认为这个对象不可达,也就是这个对象可以被回收。

可以看到一个对象是否存活与引用(reference)有关。Java中定义了4种引用类型

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用

Java定义了Reference类来描述引用

强引用(StrongReference)

强引用就是最基本的引用方式,引用的是另一块内存的起始地址。强引用的对象回收需要基于可达性分析,当对象不可达时才会被回收

Object obj = new Object()

软引用(SoftReference)

软引用是用来描述一些还有用,但不是必需的对象。对于软引用关联的对象,在系统发生OOM之前,会回收这些对象,如果回收完,内存依旧不够,系统才会抛出OOM。Java定义了SoftReference来实现软引用。

SoftReference<Object> sRef = new SoftReference<Object>()

下面用一个例子来说明软引用

/**
 * -Xmx3m 设置堆大小为3M
 * @author wbl
 * @date 2020-02-22
 */
public class ReferenceTest {
    private List<RefObj> refObjs = new ArrayList<>();

    private SoftReference<RefObj> ref = new SoftReference<RefObj>(createRefObj(4096*256));//1m

    /**
     * 往堆中新增对象
     */
    public void add(){
        //0.5m
        refObjs.add(createRefObj(4096*128));
    }

    /**
     * 创建对象
     * @param dataSize 对象大小
     * @return
     */
    private RefObj createRefObj(int dataSize){
        RefObj refObj = new RefObj();
        byte[] data = new byte[dataSize];
        for (int i = 0; i < dataSize; i++) {
            data[i] = Byte.MAX_VALUE;
        }
        refObj.setData(data);
        return refObj;
    }

    /**
     * 验证软引用对象是否被回收
     */
    public void validRef(){
        System.out.println(ref.get());
    }

    public static void main(String[] args) {
        ReferenceTest referenceTest = new ReferenceTest();
        for (int i = 0; i < 10; i++) {
            //不停新增堆大小
            referenceTest.add();
            //新增后查看SoftReference中的对象是否被回收
            referenceTest.validRef();
        }

    }

    private class RefObj{
        private byte[] data;

        public byte[] getData() {
            return data;
        }

        public void setData(byte[] data) {
            this.data = data;
        }
    }
}

运行程序可以看到如下结果

wbl.reference.ReferenceTest$RefObj@60e53b93
wbl.reference.ReferenceTest$RefObj@60e53b93
null
null
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at wbl.reference.ReferenceTest.createRefObj(ReferenceTest.java:30)
    at wbl.reference.ReferenceTest.add(ReferenceTest.java:20)
    at wbl.reference.ReferenceTest.main(ReferenceTest.java:49)

可以发现,在开始的时候,软引用的对象还没被回收,但是随着堆内存逐渐减少,在OOM之前,软引用的对象被回收了。

弱引用(WeakReference)

弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
Java定义了WeakReference来实现弱引用。

WeakReference<Object> wRef = new WeakReference<Object>()

举个例子说明下

/**
 * @author wbl
 * @date 2020-02-22
 */
public class TestWeakReference {

    public static void main(String[] args) {
        TestWeakReference rObj = new TestWeakReference();
        // 声明弱引用
        WeakReference<TestWeakReference> wRef = new WeakReference<TestWeakReference>(rObj);
        System.out.println("before GC:" + wRef.get());

        // 去掉强引用
        rObj = null;
        System.gc();
        System.out.println("after GC:" + wRef.get());
    }
}

运行结果如下

before GC:wbl.reference.TestWeakReference@60e53b93
after GC:null

可以看到GC后,弱引用引用的对象被回收。这里的一个关键语句是rObj=null,如果我们把这句注释掉会出现什么情况呢?大家可以试下。

虚引用(PhantomReference)

虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知

虚引用的用法和软引用/弱引用也很类似,只是在构造的时候,需要指定一个队列

//引用队列,当引用的对象被回收后,Reference对象本身会被添加到referenceQueue中,相当于得到了一个通知
//软引用/弱引用中都有此构造参数,只是在虚引用中此参数变成必传了而已
ReferenceQueue<RefObj> referenceQueue = new ReferenceQueue<>();

PhantomReference<RefObj> ref = new PhantomReference<RefObj>(refObj,referenceQueue);

SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj,referenceQueue);

WeakReference<RefObj> ref = new WeakReference<RefObj>(refObj,referenceQueue);

要注意理解这里引用队列存储的是Reference对象,要注意区分引用对象与被引用对象的关系。SoftReference, WeakReference, PhantomReference这些本身也是对象,他们都是Reference的子类

Reference ----> 被引用对象

用一个例子来说明

public class TestReferenceQueue {
    public static void main(String[] args) {
        ReferenceQueue rq = new ReferenceQueue();
        WeakReference wr = new WeakReference(new TestReferenceQueue(), rq);
        System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("队列中对象:" + rq.poll());
        /**
         * TestReferenceQueue中的对象只有一个引用 就是wr弱引用
         * 因此直接调用gc就可以
         */
        System.gc();
        System.out.println("弱引用对应的对象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("队列中对象:" + rq.poll());
    }
}

运行结果

弱引用对应的对象:wbl.reference.TestReferenceQueue@60e53b93, 弱引用本身:java.lang.ref.WeakReference@5e2de80c
队列中对象:null
弱引用对应的对象:null, 弱引用本身:java.lang.ref.WeakReference@5e2de80c
队列中对象:java.lang.ref.WeakReference@5e2de80c

可以看到在弱引用对应的对象被回收后,该弱引用对象本身也进入到了ReferenceQueue中。 软引用,虚引用也是如此。

总结

引用 GC策略 Reference对象
强引用 对象不可达时
软引用 OOM之前回收 SoftReference
弱引用 下一次GC前 WeakReference
虚引用 未知,也就是随时可能被回收 PhantomReference

参考文献


Reprint please specify: wbl Java Reference--1

Previous
Java Reference--2 Java Reference--2
Reference生命周期当Reference引用的对象不可达时,Reference对象自身会被放入一个pending队列。同时有一个引用处理线程会不断从队列里面拉取Reference对象进行处理。 Reference成员变量// Refe
2020-02-23
Next
Kafka——消息存储与处理-3 Kafka——消息存储与处理-3
消息存储存储路径kakfa的消息都会持久化到磁盘,并以日志文件的方式存储。日志文件的保存路径配置在config/server.properties中 # A comma seperated list of directories under
2020-02-15