Java魔法堂:四种引用类型、ReferenceQueue和WeakHashMap

一、前言                            

  JDK1.2以前只提供一种引用类型——强引用 Object obj = new Object(); 。而JDK1.2后我们多另外的三个选择分别是软引用 java.lang.ref.SoftReference 、弱引用 java.lang.ref.WeakReference 和虚引用 java.lang.ref.PhantomReference 。下面将记录对它们和相关连的引用队列 java.lang.ref.ReferenceQueue 和 java.util.WeakHashMap 的学习笔记。

二、四种引用类型                        

  1. 强引用(Strong Reference)

     最常用的引用类型,如Object obj = new Object(); 。只要强引用存在则GC时则必定不被回收。

2. 软引用(Soft Reference)

     用于描述还游泳但非必须的对象,当堆将发生OOM(Out Of Memory)时则会回收软引用所指向的内存空间,若回收后依然空间不足才会抛出OOM。

     一般用于实现内存敏感的高速缓存。

        示例:实现学生信息查询操作时有两套数据操作的方案

                一、将得到的信息存放在内存中,后续查询则直接读取内存信息;(优点:读取速度快;缺点:内存空间一直被占,若资源访问量不高,则浪费内存空间)

                二、每次查询均从数据库读取,然后填充到TO返回。(优点:内存空间将被GC回收,不会一直被占用;缺点:在GC发生之前已有的TO依然存在,但还是执行了一次数据库查询,浪费IO)

       通过软引用解决:

ReferenceQueue q = new ReferenceQueue();

// 获取数据并缓存
Object obj = new Object();
SoftReference sr = new SoftReference(obj, q);

// 下次使用时
Object obj = (Object)sr.get();
if (obj == null){
  // 当软引用被回收后才重新获取
  obj = new Object();
}

// 清理被收回后剩下来的软引用对象
SoftReference ref = null;
while((ref = q.poll()) != null){
  // 清理工作
}

  3. 弱引用(Weak Reference)

      发生GC时必定回收弱引用指向的内存空间。

  4. 虚引用(Phantom Reference)

      又称为幽灵引用或幻影引用,,虚引用既不会影响对象的生命周期,也无法通过虚引用来获取对象实例,仅用于在发生GC时接收一个系统通知。

  那现在问题来了,若一个对象的引用类型有多个,那到底如何判断它的可达性呢?其实规则如下:

  1. 单条引用链的可达性以最弱的一个引用类型来决定;   2. 多条引用链的可达性以最强的一个引用类型来决定;

     我们假设图2中引用①和③为强引用,⑤为软引用,⑦为弱引用,对于对象5按照这两个判断原则,路径①-⑤取最弱的引用⑤,因此该路径对对象5的引用为软引用。同样,③-⑦为弱引用。在这两条路径之间取最强的引用,于是对象5是一个软可及对象(当将要发生OOM时则会被回收掉)。

  软引用、弱引用和虚引用均为抽象类 java.lang.ref.Reference 的子类,而与引用队列和GC相关的操作大多在抽象类Reference中实现。

三、引用队列(java.lang.ref.ReferenceQueue)       

  引用队列配合Reference的子类等使用,当引用对象所指向的内存空间被GC回收后,该引用对象则被追加到引用队列的末尾(源码中 boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */ 说明只供Reference实例调用,且仅能调用一次)。引用队列有如下实例方法:

Reference<? extends T> ReferenceQueue#poll() ,从队列中出队一个元素,若队列为空则返回null。

Reference<? extends T> ReferenceQueue#remove() ,从队列中出队一个元素,若没有则阻塞直到有元素可出队。

Reference<? extends T> ReferenceQueue#remove(long timeout) ,从队列中出队一个元素,若没有则阻塞直到有元素可出队或超过timeout指定的毫秒数(由于采用wait(long timeout)方式实现等待,因此时间不能保证)。

四、 java.lang.ref.Reference                

   Reference内部通过一个 {Reference} next 的字段来构建一个Reference类型的单向链表。另外其内部还包含一个 ReferenceQueue<? super T> queue 字段存放引用对象对应的引用队列,若Reference子类构造函数中没有指定则使用ReferenceQueue.NULL,也就是说每个软、弱、虚引用对象必定与一个引用队列关联。

   Reference还包含一个静态字段 {Reference} pending (默认为null),用于存放被GC回收了内存空间的引用对象单向链表。Reference通过静态代码块启动一个优先级最高的守护线程检查pending字段为null,若不为null则沿着单向链表将引用对象追加到该引用对象关联的引用队列当中(除非引用队列为ReferenceQueue.NULL)。守护线程的源码如下:

    public void run() {
        for (;;) {

        Reference r;
        synchronized (lock) {
        // 检查pending是否为null
            if (pending != null) {
            r = pending;
            Reference rn = r.next;
            pending = (rn == r) ? null : rn;
            r.next = r;
            } else {
            try {
          // pending为null时,则将当前线程进入wait set,等待GC执行后执行notifyAll
                lock.wait();
            } catch (InterruptedException x) { }
            continue;
            }
        }

        // Fast path for cleaners
        if (r instanceof Cleaner) {
            ((Cleaner)r).clean();
            continue;
        }
        // 追加到对应的引用队列中
        ReferenceQueue q = r.queue;
        if (q != ReferenceQueue.NULL) q.enqueue(r);
        }
    }

  注意:由于通过静态代码块进行线程的创建和启动,因此Reference的所有子类实例均通过同一个线程进行向各自的引用队列追加引用对象的操作。

五、java.util.WeakHashMap                 

  由于WeakHashMap的键对象为弱引用,因此当发生GC时键对象所指向的内存空间将被回收,被回收后再调用size、clear或put等直接或间接调用私有expungeStaleEntries方法的实例方法时,则这些键对象已被回收的项目(Entry)将被移除出键值对集合中。

  下列代码将发生OOM

public static void main(String[] args) throws Exception {

        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();

        for (int i = 0; i < 1000; i++) {
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();
            d.put(new byte[1000][1000], new byte[1000][1000]);
            maps.add(d);
            System.gc();
            System.err.println(i);
        }
    }

  而下面的代码因为集合的Entry被移除因此不会发生OOM

public static void main(String[] args) throws Exception {  
  
        List<WeakHashMap<byte[][], byte[][]>> maps = new ArrayList<WeakHashMap<byte[][], byte[][]>>();  
  
        for (int i = 0; i < 1000; i++) {  
            WeakHashMap<byte[][], byte[][]> d = new WeakHashMap<byte[][], byte[][]>();  
            d.put(new byte[1000][1000], new byte[1000][1000]);  
            maps.add(d);  
            System.gc();  
            System.err.println(i);  
  
            for (int j = 0; j < i; j++) {
                // 触发移除Entry操作
                System.err.println(j+  " size" + maps.get(j).size());  
            }  
        }  
    }  

六、总结                            

  尊重原创,转载请注明

七、参考                            

《WeakHashMap的神话》http://www.javaeye.com/topic/587995

http://hongjiang.info/java-referencequeue/

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏木木玲

Reference 、ReferenceQueue 详解

3287
来自专栏微信公众号:Java团长

2017年Java面试题整理

面试是我们每个人都要经历的事情,大部分人且不止一次,这里给大家总结最新的2017年面试题,让大家在找工作时候能够事半功倍。

892
来自专栏java一日一条

Java 反射机制应用实践

Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis都可以看见反射的身影。通过反射机制我们可以在运行期间获取对象的类型信息,利...

1602
来自专栏小勇DW3

迭代器模式以及对内部类的运用

上一篇文章写了static的作用,其中有部分是介绍了内部类和静态内部类,下面就结合设计模式中的迭代器模式,介绍一下内部类的好处;

753
来自专栏编程微刊

用原型链的方式写一个类和子类

1472
来自专栏大闲人柴毛毛

Redis源码分析(一)——Redis数据结构-字符串SDS

1. SDS简介 Redis中使用的字符串均为『简单动态字符串』(Simple Dynamic String),简称SDS。 SDS是在C字符串的基础上进行了...

2834
来自专栏IT可乐

Redis详解(四)------ redis的底层数据结构

  上一篇博客我们介绍了 redis的五大数据类型详细用法,但是在 Redis 中,这几种数据类型底层是由什么数据结构构造的呢?本篇博客我们就来详细介绍Redi...

1460
来自专栏xingoo, 一个梦想做发明家的程序员

套接口编程

1 struct in_addr{ 2 in_addr_t s_addr; 3 }; 4 struct sockaddr_in{ 5 ...

2048
来自专栏java技术学习之道

java50道基础面试题

1657
来自专栏java一日一条

115个Java面试题和答案——终极列表(上)

本文我们将要讨论Java面试中的各种不同类型的面试题,它们可以让雇主测试应聘者的Java和通用的面向对象编程的能力。下面的章节分为上下两篇,第一篇将要讨论面向对...

781

扫码关注云+社区

领取腾讯云代金券