四种引用类型、ReferenceQueue和WeakHashMap

#1、前言                            java

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

#2、四种引用类型                        缓存

1. 强引用(Strong Reference)

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

2. 软引用(Soft Reference)

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

通常用于实现内存敏感的高速缓存线程

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

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

2、每次查询均从数据库读取,而后填充到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. 单条引用链的可达性以最弱的一个引用类型来决定;
  1. 多条引用链的可达性以最强的一个引用类型来决定;

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

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

#3、引用队列(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)方式实现等待,所以时间不能保证)。

#4、 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的全部子类实例均经过同一个线程进行向各自的引用队列追加引用对象的操做。

#5、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());  
            }  
        }  
    }
相关文章
相关标签/搜索