Java拾遗

Java拾遗:005 - Java的四种引用类型

2018-08-03  本文已影响17人  ed72fd6aaa3c

简介

Java中为了让程序员可以自己控制对象生命周期,提供了四种引用方式,都继承自java.lang.ref.Reference类,它们分别是:强引用、软引用、弱引用、虚引用。

强引用(FinalReference / Finalizer)

在Java中像Object obj = new Object();这种形式产生的引用都是强引用。强引用可以直接访问引用对象,而且强引用只在存在,那么引用对象就不会被JVM垃圾回收,即使JVM抛出OOM异常的时候,也不回收强引用引用的对象,所以不正确的使用方式可能会造成内存泄漏。
表示强引用的类java.lang.ref.FinalReference是包级范围访问的,所以外部不能直接使用,而其子类java.lang.ref.Finalizer同样是包级范围访问,所以一般程序员不会使用它们。
下面是一段测试代码,测试强引用对象是否会被GC掉(在VM中增加-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx5m参数,用于打印GC日志和设置JVM最大内存)。

        // 通过new的方式生成一个强引用
        Object obj = new Object();

        // java.lang.Object@5702b3b1
        System.out.println(obj);

        // 执行GC
        System.gc();

        // 执行GC后,强引用不会被回收
        // java.lang.Object@5702b3b1
        System.out.println(obj);

        // 分配一块大内存(5M),此时JVM内存耗尽,实际会抛出OOM错误,这里捕获掉,观察软引用引用的对象是否被回收
        // java.lang.OutOfMemoryError: Java heap space
        try {
            byte[] buf = new byte[5 * 1024 * 1024];
        } catch (Throwable t) {
            System.out.println(t.getMessage());
        }

        // 即使内存溢出,也不回收强引用对象
        // java.lang.Object@5702b3b1
        System.out.println(obj);

软引用(SoftReference)

软引用用于描述有用但不是很重要的对象,这种对象在JVM内存充足时不会被GC掉,但在内存紧张甚至OOM时,会被GC。
软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

        // 将这个对象赋值给软引用
        SoftReference<Object> sr = new SoftReference<>(new Object());

        // soft_ref_1 = java.lang.Object@155ec9f4
        System.out.println("soft_ref_1 = " + sr.get());

        // 此时对象只有一个软件引用了,执行GC
        System.gc();

        // 说明在内存充足的情况下,GC不会回收软引用对象
        // soft_ref_2 = java.lang.Object@5a6d6fc5
        System.out.println("soft_ref_2 = " + sr.get());

        // 分配一块大内存(5M),此时JVM内存耗尽,实际会抛出OOM错误,这里捕获掉,观察软引用引用的对象是否被回收
        // java.lang.OutOfMemoryError: Java heap space
        try {
            byte[] buf = new byte[5 * 1024 * 1024];
        } catch (Throwable t) {
            // t.printStackTrace();
        }

        // 说明JVM内存耗尽时会回收软引用对象
        // soft_ref_3 = null
        System.out.println("soft_ref_3 = " + sr.get());

弱引用(WeakReference)

弱引用是一种比软引用强度更弱的引用,功能与软引用类似,但区别在于,只要JVM发生GC,弱引用引用的对象就会被回收。

        ReferenceQueue<Object> queue = new ReferenceQueue<>();

        // 构造一个弱引用
//        WeakReference<Object> ref = new WeakReference<>(new Object());
        WeakReference<Object> ref = new WeakReference<>(new Object(), queue);

        // weak_ref_1 = java.lang.Object@5702b3b1
        System.out.println("weak_ref_1 = " + ref.get());
        // null
        System.out.println(queue.remove(200L));

        // 此时对象只有一个软件引用了,执行GC
        System.gc();

        // 即使在内存充足的情况下,只要发生GC就会回收弱引用对象
        // weak_ref_2 = null
        System.out.println("weak_ref_2 = " + ref.get());
        // 当垃圾对象被回收后,弱引用被加入到引用队列中
        // java.lang.ref.WeakReference@22927a81
        System.out.println(queue.remove(200L));

        System.gc();
        // null
        System.out.println(queue.remove(200L));

虚引用(PhantomReference)

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期,这种引用引用的对象随时都可能被回收,而且它的get方法永远返回null。

public class PhantomReference<T> extends Reference<T> {

    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

}

虚引用必须和引用队列一起使用,用于跟踪垃圾回收过程。

public class PhantomReferenceTest {

    static class MyType {
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("--finalize--");
        }

        @Override
        public String toString() {
            return "--string--";
        }
    }

    @Test
    public void test() throws InterruptedException {

        // 构造虚引用必须传入引用队列
        ReferenceQueue<MyType> queue = new ReferenceQueue<>();
        PhantomReference<MyType> ref = new PhantomReference<>(new MyType(), queue);

        // 这种引用跟没有引用几乎没有分别,任何时候通过get方法取得的都是空值
        // phantom_ref_1 = null
        System.out.println("phantom_ref_1 = " + ref.get());

        // 执行GC,第一次:通过观察输出日志发现,JVM找到了垃圾对象,并调用finalize方法回收内存,但没有立即加入回收队列
        System.gc();
        System.out.println("GC - 1");
        // --finalize--
        // null
        System.out.println(queue.remove(200L));

        // 执行GC,第二次,通过观察输出日志发现,第二次GC后JVM才真正清除垃圾对象,并将其加入引用队列(所以才能从队列中弹出值)
        System.gc();
        System.out.println("GC - 2");
        // java.lang.ref.PhantomReference@32a1bec0
        System.out.println(queue.remove(200L));

        // 执行GC,第三次,后面的GC与该对象已经无关
        System.gc();
        System.out.println("GC - 3");
        // null
        System.out.println(queue.remove(200L));

    }

}

结语

在一次面试中面试官问到了这个问题,当时我只知道强引用和弱引用(实际上是软引用),这种基础的问题答不上来还真是挺尴尬的。

参考资料:

上一篇下一篇

猜你喜欢

热点阅读