Java 中的强引用 、软引用、弱引用、虚引用

这里整理一下之前一直理解得不是很清楚的 Java 的引用;

Java 开发不像 C 语言有指针,不能通过编码回收内存,完全靠垃圾回收器不定时来进行垃圾回收;

虽然垃圾回收器的工作是靠 JVM 来自动控制,但是做为 Java 程序员仍然可以通过编程在一定程序上与垃圾回收器进行交互,以帮助程序员稍微精细的控制内存回收,帮助垃圾回收器更好的管理内存的回收工作;

Java 中有存在着四种引用类型:强引用 、软引用、弱引用、虚引用;四种类型的引用强度由强至弱,依次递减。

强引用

强引用是 Java 中最常见的也是最直接的引用方式,例如

Person p = new Person("Tom");  

这样的代码就会生成一个 Person对象,和一个指向此对象的强引用;通常一个对象可能有多个强引用,只要对象存在强引用,垃圾回收器就不会回收这块内存。堆中的存在强引用的对象越来越多,最终内存不够用时,JVM就会抛出 java.lang.OutOfMemoryError这个错误!

软引用

软引用相对强引用,引用强度略低,通过java.lang.ref.SoftReference来实现,与强引用的区别就是:垃圾回收器在 JVM 内存不够用的时候才回收回软引用所引用的对象内存;如果已经释放掉所有的软引用所引用的对象的内存,依旧内存不够时才会抛出java.lang.OutOfMemoryError错误;因为软引用的这个特性,它很适合拿来实现程序中缓存数据,比喻程序启动时需要从数据库中读取大量没有太高实时性要求的配置数据时,就可以使用软引用来缓存这些数据,不必每次访问时去查数据库,只有当内存不够用时缓存的这块数据空间被回收,才无法命中去查数据库,这样减少了数据库操作;

示例代码:

public class DBConfigCache {  
    private SoftReference<Config> configCache;

    public DBConfigCache() {
    }

    private Config readConfigFromDB() {
         return new Confit();//忽略从数据库查的代码
    }
    public Config getConfig() {
        Config config = configCache == null ? null : configCache.get();//获取得配置对象的强引用;
        if(config == null) {
             //第一次读时为空
             //缓存的数据被回收时为空,
             //从数据库读
             config = readConfigFromDB();
             configCache = new SoftReference<>(config);
        }
        return config;
    }
}

弱引用

弱引用,相对于软引用,引用强度弱低,通过java.lang.ref.WeakReference来实现,弱引用所引用的对象,不保证不被垃圾回收器回收。只有弱引用的对象,不存在其他引用类型,会被垃圾回收器回收;弱引用应用场景很少,通常用来解决对象间存活有耦合关系的引用问题。最常见就是使用在集合中,比喻当一个对象被当做键值往哈希表中来存放一个值,希望此对象被垃圾回收器回收后也将在哈希表中对应存放的值也删除掉,那么就要可以使用弱引用实现的java.util.WeakHashMap;当只哈希表中的键对象不存其他引用类型时,被垃圾回收器回收后,同时也会删除键对应的值;

可以通过如下代码测试:

private Map<Person, Object> map = new WeakHashMap<Person, Object>();

    public void testWeakHashMap () {

        Person p1 = new Person("test2");
        Person p2 = new Person("test2");
        map.put(p1, new Object());
        map.put(p2, new Object());

        System.out.println("哈希表大小:" + map.size());

        System.gc();
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("哈希表大小:" + map.size());


        p1 = null;
        System.gc();
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("哈希表大小:" + map.size());

    }

方法执行结果:

哈希表大小:2
哈希表大小:2
哈希表大小:1

虚引用

虚引用,使用java.lang.ref.PhantomReference来实现;虚引用跟字面意义一样,如果一个对象存在虚引用,则形同虚设,与没有引用一样;实现的时候需要得传一个引用队列java.lang.ref.ReferenceQueue;我们知道内存回收时因为回收算法(比喻『标记-清除-整理』)的原因是在多个执行周期内进行的;在对象被垃圾回收器识别为要回收时,会将此虚引用加到引用队列中,引用队列的remove方法会一直阻塞到有引用加入队列;因此借助引用队列的remove可以在对象内存被释放前做一些业务,比喻获取对象释放的时间;需要指出来的时,因为虚引用get方法永远返回null,所以在对象内存被释放时,就不会出现自定义对象的finalize时逃逸回收的问题; 不过也不绝对,通过引用对象反射依旧有办法可以获取到引用的对象,但是这是非常规操作,基本可以忽略;感觉这虚引用没啥用;

感谢 Relucent 的例子:

https://github.com/Relucent/yyl_example/blob/master/src/main/java/yyl/example/basic/reference/PhantomReferenceTest.java

我将例子修了一下:

final ReferenceQueue<Person> queue = new ReferenceQueue<>();  
Person person = new Person("John");

new Thread() {  
    public void run() {
        try {
//会一直阻塞直到`虚引用`加到队列中;
Reference<Person> reference = (Reference<Person>) queue.remove();  
System.out.println("垃圾回收器, 准备回收对象: " + reference.get());

System.out.println("垃圾回收器, 准备回收对象(暴力获取): " + forceGet(reference));

        } catch (InterruptedException e) {                      
e.printStackTrace();  
        }
}}.start();

PhantomReference<Person> reference = new PhantomReference<>(person, queue);

//虚引用 get 永远返回 null        
System.out.println("reference.get() -> " + reference.get());

//直接读取引用字段
System.out.println("reference.referent -> " + forceGet(reference));

Thread.sleep(2000);

person = null;

System.gc();  

执行结果:

reference.get() -> null  
reference.referent -> Person{name='John'}  
垃圾回收器, 准备回收对象: null
垃圾回收器, 准备回收对象(暴力获取): Person{name='John'}

参考内容:

http://blog.csdn.net/xiaofengcanyuexj/article/details/45271195 http://www.infoq.com/cn/articles/cf-java-garbage-references http://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/index.html