美文网首页程序员
Java引用类型分析

Java引用类型分析

作者: bing__chen | 来源:发表于2017-03-27 23:46 被阅读0次

    概述

    java.lang.ref 类库包含一组类,为垃圾回收提供了更大的灵活性。

    java.lang.ref 有三个继承自抽象类 Reference 的类:

    Reference类图

    这三个类为垃圾回收器(GC)提供了不同级别的提示,使得GC以不同的策略回收对象。

    StrongReference

    强引用是使用最普遍的引用,它是默认的引用类型,不需要显式声明,在java.lang.ref中没有实际的类对应,可以把它理解为Java的内置省略默认引用类型。

    具有强引用的对象, 只要对象是“可获得的”(reachable),GC就承诺不会回收对象,即使JVM内存不足,抛出OutOfMemoryError异常。

    对象是“可获得的”(reachable),是指此对象可在程序中的某处找到。这意味着你在内存栈中有一个普通的引用,而它正指向此对象;也可能是你的引用指向某个对象,而那个对象含有另一个引用,指向正在讨论的对象;也可能有更多的中间链接。

    @Test
    public void strongReferenceTest() {
        Object obj = new Object();
        System.gc();
        assertThat("obj没被回收", obj, not(nullValue()));
    }
    

    SoftReference

    只具有软引用的对象,GC承诺在JVM内存充足的时候不回收对象。

    @Test
    public void softReferenceTest() {
        SoftReference<Object> objSoftReference = new SoftReference<Object>(new Object());
        
        int index = 0;
        long[][] vars = new long[1024][];
        
        long maxMemory;
        long freeMemory;
        
        while(objSoftReference.get() != null) {
            maxMemory = Runtime.getRuntime().maxMemory(); //最大可用内存
            freeMemory = Runtime.getRuntime().freeMemory(); //当前JVM空闲内存
            System.out.printf("maxMemory = %s, freeMemory = %s\n", maxMemory, freeMemory);
        
            vars[index++] = new long[1024];
            System.gc();
        }
        assertThat("obj被回收了", objSoftReference.get(), nullValue());
    }
    

    执行上面的用例,刚开始objSoftReference引用的对象不会被GC回收,随着内存逐渐被吃掉,JVM开始觉得内存匮乏了才回收objSoftReference引用的对象。

    由此可见,SoftReference在内存充足的时候保持对象,在内存匮乏的时候释放对象。这种回收策略适合应用在内存敏感的高速缓存的场景。

    注意: 执行用例前需要设置JVM参数: -Xmx1m,限制jvm的Java Heap最大值。

    设置其他的值该用例可能执行失败,原因是:

    1. new long[1024]可能越过了JVM内存不充足的判断边界。
    2. System.gc()调用频率的限制。

    WeakReference

    只具有弱引用的对象,GC执行时会马上回收对象。

    @Test
    public void WeakReferenceTest() throws InterruptedException {
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        WeakReference<Object> objWeakReference = new WeakReference<Object>(new Object(), referenceQueue);
    
        assertThat("还没有执行GC, obj还没被回收", objWeakReference.get(), not(nullValue()));
        assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());
    
        System.gc();
        Thread.sleep(500);  // 确保GC执行完成
    
        assertThat("执行GC后, obj马上被回收了", objWeakReference.get(), nullValue());
        assertThat("执行GC后, objWeakReference被放入referenceQueue", objWeakReference, equalTo((Reference)referenceQueue.poll()));
    }
    

    由于GC线程的优先级比较低,不一定会很快执行GC,所以只具有弱引用的对象可能会继续存活一段时间,这段时间内可以通过get()方法继续获得引用的对象。当GC回收对象后会把objWeakReference放入referenceQueue队列中。

    PhantomReference

    只具有虚引用的对象,和 没有任何引用一样 ,无论它是否被回收,你永远也取不到引用的对象了,并且GC执行时会马上回收对象。

    @Test
    public void PhantomReferenceTest() throws InterruptedException {
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        PhantomReference<Object> objPhantomReference = new PhantomReference<Object>(new Object(), referenceQueue);
    
        assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
        assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());
    
        System.gc();
        Thread.sleep(500);  // 确保GC执行完成
    
        assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
        assertThat("执行GC后, objPhantomReference被放入referenceQueue", objPhantomReference, equalTo((Reference)referenceQueue.poll()));
    }
    

    换言之,当一个只具有虚引用的对象,你已经失去了对它的所有控制权。唯一你可知的是: 对象是否被GC回收了,当GC回收对象后和WeakReference一样,GC会把objPhantomReference放入referenceQueue队列中。

    WeakReference vs PhantomReference

    目前为止,我们已经可以总结出WeakReferencePhantomReference的一些相同点和不同点。

    相同点:

    • 当GC执行时,两者引用的对象都会被回收。
    • 对象被回收后,引用对象本身都会被放入一个ReferenceQueue队列中。

    不同点:

    • GC回收引用的对象前,WeakReference还有机会获得引用的对象,而PhantomReference永远失去了和引用的对象之间的联系。
    • 使用SoftReferenceWeakReference时,你可以选择是否要将它们放入ReferenceQueue中。而PhantomReference只能依赖于ReferenceQueue,否则毫无用处。

    除了以上的不同点外,WeakReferencePhantomReference之间还有一个最大的不同点,先看用例:

    Object obj = null;
    
    @Test
    public void WeakReferenceWhenFinalizeTest() throws InterruptedException {
    
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        WeakReference<Object> objWeakReference = new WeakReference<Object>(
            new Object() {
                public void finalize() {
                    obj = this;
                }
            }, referenceQueue);
    
        assertThat("还没有执行GC, obj还没被回收", objWeakReference.get(), not(nullValue()));
        assertThat("还没有执行GC, referenceQueue为空", referenceQueue.poll(), nullValue());
    
        System.gc();
        Thread.sleep(500);  // 确保GC执行完成
    
        assertThat("执行GC后, obj没有被回收,但是无法获取到对象", objWeakReference.get(), nullValue());
        assertThat("执行GC后, obj没有被回收,objWeakReference被放入referenceQueue", objWeakReference, equalTo((Reference)referenceQueue.poll()));
    }
    
    Object obj = null;
    
    @Test
    public void PhantomReferenceWhenFinalizeTest() throws InterruptedException {
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        PhantomReference<Object> objPhantomReference = new PhantomReference<Object>(
            new Object() {
                public void finalize() {
                    obj = this;
                }
            }, referenceQueue);
    
        assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
        assertThat("还没有执行GC, obj没有被回收,referenceQueue为空", referenceQueue.poll(), nullValue());
    
        System.gc();
        Thread.sleep(500);  // 确保GC执行完成
    
        assertThat("无法通过虚引用获取到对象", objPhantomReference.get(), nullValue());
        assertThat("执行GC后, referenceQueue为空", referenceQueue.poll(), nullValue());
    }
    

    GC执行时,引用的对象通过finalize()再次将自己激活,GC最终并没有释放引用的对象。

    这时:

    • WeakReference已经无法获得引用的对象,并且WeakReference对象被放入了ReferenceQueue
    • PhantomReference对象并没有被放入ReferenceQueue

    所以,PhantomReference区别于WeakReference最大的不同是PhantomReference对象只有在对象真正被回收后才会被放入ReferenceQueue

    总结

    如果你想继续持有对某个对象的引用,希望以后还能够访问到该对象,同时也允许垃圾回收器释放它,这时就应该使用Reference对象。

    StrongReferenceSoftReferenceWeakReferencePhantomReference由强到弱排列,应用的场景也各不相同。

    • Softreference: 只在内存不足时才被回收,主要用以实现内存敏感的高速缓存。
    • WeakReference: 主要用以实现 规范映射 ,具体的实践可以查看WeakHashMap的实现。
    • Phantomreference: 可以追踪对象的回收事件,主要用以执行回收前的清理工作,它比finalize()更灵活。

    相关文章

      网友评论

        本文标题:Java引用类型分析

        本文链接:https://www.haomeiwen.com/subject/qrrrottx.html