分享按钮
RSS订阅

清逸成风_【转载】Reference

作者: 清逸成风 |来源:转载| 标签: java   清逸成风   java编程  

本文转载自http://shift-alt-ctrl.iteye.com/blog/1839163

 

前言:java提供了几种引用类型,以方便编程者跟踪对象生命周期。

    Reference抽象类提供了除强引用之外的其他引用类型基本功能。ReferenceQueue是一个队列(内部实现为一个Reference的列表),用于注册那些GC检测到不可达(即将会被回收)对象。

    每个reference对象都可以"注册"相关的引用对象,当此对象即将回收(已经回收),将会把此reference对象加入到referenceQueue中,对于外部程序,可以通过轮训queue来获取跟踪的对象,继而采取相应的措施。

 

    java.lang.ref包下的类,提供了java代码与jvm简单交互的功能。

一. Reference此类声明了如下几个重要的方法:

  • Reference(T referent)
  • Reference(T referent,ReferenceQueue queue),向reference"注册"需要引用的对象以及监控queue.默认queue为NULL,即不监控。
  • T get():返回"注册"的引用对象,如果此对象已回收,将返回null。
  • clear():清除reference的引用对象,将referent置为null,但是此操作不会触发referenceQueue对Reference的enqueue操作。(在VM真正决定回收时,触发enqueue)
  • boolean isEnqueued():检测Reference对象是否已经enqueue,此方法可以被程序或者GC调用。
  • boolean enqueue():将"注册"的对象,加入referenceQueue。此方法只会被java程序调用,对于GC调用会更加直接(直接用queue操作),在绝大部分情况下,我们都不会有机会直接接触上述方法。

       Reference类(子类)有一个static pending(也是Reference类型,自己也有个next属性,也是Reference类型,类似Node)属性,这个属性最终导致Reference对象形成一个环形链表(开始时自己头尾相接),这个属性就是等待enqueued的列表,GC会把Reference添加到此列表中。即把即将enqueue的Reference对象通过next一个一个的串了起来。

      在Reference类中,还有个ReferenceHandler线程类,static类型.在Reference类加载时通过static区块,启动了此线程。此线程就是检测static pending是否为空,即此时否有即将equeue的reference,如果有就把此reference加入到自己的queue中:

      

Java代码   收藏代码
  1. ReferenceQueue q = ref.queue;  
  2. q.enqueue(ref));  

 

    并将链表移动到next;一致循环,直到pending != null,如果pending为null将会触发lock.wait(),lock为static,类级别,在pending改变时会触发lock被打断。

 

 

二.ReferenceQueue

    此类就是一个queue,内部通过链表实现。 不过这个内部链表维护的是Reference列表,有个属性为Reference head,每个加入queue的Reference都会将自己的next只指向head,head指向自己,形成环形链表,数据结构为循环队列。

    1)Reference poll(): 弹出一个reference,无阻塞,即从队列中移除。

    2)Reference remove(long timeout): 从stack中移除一个,如果stack中无数据则阻塞timeout。其功能和poll一样,ReferenceQueue还有一个方法为remote(),如果queue中无数据将一直阻塞。

 

    如果GC决定"注册"的对象reachability 改变(变成不可达),将会把其被引用的Reference加入到queue中,对于程序可以通过poll操作将他们从队列中移除,此时reference就能为你做一些事情。 ReferenceQueue就是一个获取对象被GC的“通知”队列,对这些reference感兴趣的程序,可以通过操作它们注册的queue,来获取通知。参见(WeakHashMap实现)

 

三.引用类型

    每种引用类型表示不同级别的reachability(可达性),soft引用可以用于实现内存敏感的cache,weak引用用于实现规范化的映射,phantom引用可以在对象回收前作一些操作,比java的Finalization机制更加灵活。

reference和queue的关系是单方面的,queue不会对reference跟踪。如果注册在queue中的reference自己不可达了,那么他将永远不会被enqueued。使用reference对象的程序职责,就是确保只要程序对

 referents(reference所注册的对象)仍感兴趣,那么reference对象就要保持可达。(注意:ReferenceQueue中注册的是Reference对象,Reference对象绑定了一个“强引用”对象,如果“强引用”对象不可达,那么此绑定的reference对象将会被加入到referenceQueue中,reference对象也是一个强引用对象,因为它也是new出来的。)

 

    一些程序会启用一个线程来移除queue中的reference对象,然后操作他们,不过这有时候也是没有必要的。不过也有些有效的办法来这样操作queue,比如在一些调用频繁的操作中做移除reference操作。这也是WeakHashMap所使用的方式(在很多方法中,调用一个私有的expungeStaleEntries方法)。

 

    soft和weak引用在加入到queue之前,其绑定的“强引用”对象就已经被GC清除,即已经解除了对象引用关系(调用了reference的clear方法),那么从referenceQueue.poll()得到的reference对象,调用get方法,将得到null,我们无法“复活”那些已经被添加到queue中的“强引用”对象;对于GC而言,调用clear方法,然后将reference添加到queue,然后再执行绑定的“强引用”对象的finalize方法。因为queue.poll方法和GC线程执行finalize方法并非一个线程,因此我们无法绝对断定,poll和finalize方法调用的时间顺序。

 

    不过对于phatom引用则有些不同,GC则会首先执行绑定的“强引用”对象的finalize方法,然后才会把phatom reference对象加入到queue中。通过phantfom引用可到达的对象将仍然保持原状,所以,我们需要手动调用clear方法来解除reference与“强引用”对象的绑定关系。我们可以认为phantfom引用时finalize()机制的补充,我们可以在queue.poll()之后,做一些额外的处理。

 

    可达性:

    1) 如果一个线程无需遍历所有引用对象而直接到达一个对象,则该对象为强可达对象(strong reachable),每个对象,对于创建它的线程而言,是强可达对象。[Strong reference]

    2) 如果一个对象不是强可达对象,遍历一次软引用对象可达,那么该对象为软可达对象。[soft reference]

    3) 如果一个对象非强非软,但是通过遍历一次弱引用对象可达,那么该对象为弱可达对象。[weak reference]

    4) 如果一个对象非上述3中,那么它已经终止,并且某个虚引用在引用它,则对象为虚可达对象。[phantfom reference]

    5) 除上述之外,一个对象仍不可达,那么此对象为不可达对象,因此可以被回收。(clear)

 

    1) FinalReference: 非用户API,用于实现Finalize功能,基本不可用。唯一子类FinalReference,为protected。

    2) softReference: 软引用,据其名字,它可以适应内存需要来决定对象的生命周期,即当内存不足时(即将OOM时),GC将会决定是否清除该对象。软引用对象最常用于实现内存敏感的缓存。

 

    假定垃圾回收器确定在某一时间点某个对象是软可到达对象。这时,它可以选择自动清除针对该对象的所有软引用,以及通过强引用链,从其可以到达该对象的针对任何其他软可到达对象的所有软引用。在同一时间或晚些时候,它会将那些已经向引用队列注册的新清除的软引用加入队列。

 

    软可到达对象的所有软引用都要保证在虚拟机抛出 OutOfMemoryError 之前已经被清除。否则,清除软引用的时间或者清除不同对象的一组此类引用的顺序将不受任何约束。然而,虚拟机实现不鼓励清除最近访问或使用过的软引用。

 

    此类的直接实例可用于实现简单缓存,该类或其派生的子类还可用于更大型的数据结构,以实现更复杂的缓存。只要软引用的指示对象是强可到达对象,即正在实际使用的对象,就不会清除软引用。例如,通过保持最近使用的项的强指示对象,并由垃圾回收器决定是否放弃剩余的项,复杂的缓存可以防止放弃最近使用的项。

 

    回收时机: JVM内存不足时,将会导致那些没有强引用、但是被soft引用所的持久的对象被回收。回收后,将会立即或者不久将其softRerence加入queue。

 

    2)WeakReference: 弱引用对象,弱引用最常用于实现规范化的映射。,假定垃圾回收器确定在某一时间点上某个对象是弱可到达对象。这时,它将自动清除针对此对象的所有弱引用,以及通过强引用链和软引用,可以从其到达该对象的针对任何其他弱可到达对象的所有弱引用。同时它将声明所有以前的弱可到达对象为可终结的。在同一时间或晚些时候,它将那些已经向引用队列注册的新清除的弱引用加入队列。

 

    回收时机:生命周期最短,每次GC时(minor、full),将会导致那些没有强引用和弱引用的对象,且有weak引用,那么此对象将会被回收。

 

3)PhantfomReference:

 

    虚引用对象,在回收器确定其指示对象可另外回收之后,被加入队列(即从reference中get时返回null)。虚引用最常见的用法是以某种可能比使用 Java 终结机制更灵活的方式来指派 pre-mortem 清除操作。 如果垃圾回收器确定在某一特定时间点上虚引用的指示对象是虚可到达对象,那么在那时或者在以后的某一时间,它会将该引用加入队列。 为了确保可回收的对象仍然保持原状,虚引用的指示对象不能被检索(不能被再次强引用):虚引用的 get 方法总是返回 null。

    与软引用和弱引用不同,虚引用在加入队列时并没有通过垃圾回收器自动清除。通过虚引用可到达的对象将仍然保持原状,直到所有这类引用都被清除,或者它们都变得不可到达。

    回收时机: 之所以为虚引用,那么可以认为一个对象具有虚引用(被phantfomReference对象所引用),和没有被任何对象引用一样。这种引用不会关心对象的回收时机,只要对象被回收,将会将虚引用对象加入queue。

 

 

 

代码实例:

 

Java代码   收藏代码
  1. package com.test;  
  2. import java.lang.ref.ReferenceQueue;  
  3. import java.lang.ref.WeakReference;  
  4. import java.util.HashMap;  
  5. import java.util.Map;  
  6. /** 
  7.  * 模拟weakHashMap,当value被回收,也会在WeakKeyMap中移除 
  8.  * 对于WeakValueMap而言,value是弱引用,即可以在weakValueMap引用的情况下被回收 
  9.  */  
  10. public class WeakValueMap extends HashMap{  
  11.     private ReferenceQueue queue = new ReferenceQueue();  
  12.     private class Entry extends WeakReference{  
  13.         K key;  
  14.         Entry(K key,V value,ReferenceQueue rq) {  
  15.             super(value,rq);  
  16.             this.key= key;  
  17.         }  
  18.     }  
  19.     public void putX(K key,V value){  
  20.         expungeStaleEntries();  
  21.         Entry entry = new Entry(key, value, queue);  
  22.         super.put(key, entry);  
  23.     }  
  24.     public V getX(Object key) {  
  25.         expungeStaleEntries();  
  26.         Entry entry = (Entry)super.get(key);  
  27.         if(entry != null){  
  28.             return (V)entry.get();  
  29.         }  
  30.     return null;  
  31.     }  
  32.     public void removeX(K key){  
  33.         expungeStaleEntries();  
  34.         Entry entry = (Entry)super.get(key);  
  35.         if(entry != null){  
  36.             entry.clear();  
  37.             super.remove(key);  
  38.         }  
  39.     }  
  40.     private void expungeStaleEntries(){  
  41.         Entry entry;  
  42.         while((entry = (Entry)queue.poll()) != null){  
  43.             System.out.println("K:" + entry.key.toString());  
  44.             System.out.println("V:" + entry.get());  
  45.         super.remove(entry.key);  
  46.         }  
  47.     }  
  48. }  

 


清逸成风:www.chenzongyong.cn
逸风美图:yfmt.sinaapp.com