Java GC 相关的4种引用

1. Strong Reference

StrongReference 是 Java 的默认引用实现,它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时Java GC 执行后将会被回收

  
  
  1. @Test  
  2. public void strongReference() {   
  3. Object referent = new Object();   
  4.    
  5. /**  
  6.  * 通过赋值创建 StrongReference   
  7.  */  
  8. Object strongReference = referent;   
  9.    
  10. assertSame(referent, strongReference);   
  11.    
  12. referent = null;   
  13. System.gc();   
  14.    
  15. /**  
  16.  * StrongReference 在 GC 后不会被回收  
  17.  */  
  18. assertNotNull(strongReference);   
  19. }   
  20.  

2. WeakReference & WeakHashMap

WeakReference, 顾名思义,是一个弱引用,当所引用的对象在 JVM 内不再有强引用时, Java GC 后 weak reference 将会被自动回收

  
  
  1. @Test  
  2. public void weakReference() {   
  3. Object referent = new Object();   
  4. WeakReference<Object> weakRerference = new WeakReference<Object>(referent);   
  5.  
  6. assertSame(referent, weakRerference.get());   
  7.    
  8. referent = null;   
  9. System.gc();   
  10.    
  11. /**  
  12.  * 一旦没有指向 referent 的强引用, weak reference 在 GC 后会被自动回收  
  13.  */  
  14. assertNull(weakRerference.get());   
  15. }   
  16.  

WeakHashMap 使用 WeakReference 作为 key, 一旦没有指向 key 的强引用, WeakHashMap 在Java GC 后将自动删除相关的 entry

  
  
  1. @Test  
  2. public void weakHashMap() throws InterruptedException {   
  3. Map<Object, Object> weakHashMap = new WeakHashMap<Object, Object>();   
  4. Object key = new Object();   
  5. Object value = new Object();   
  6. weakHashMap.put(key, value);   
  7.  
  8. assertTrue(weakHashMap.containsValue(value));   
  9.    
  10. key = null;   
  11. System.gc();   
  12.    
  13. /**  
  14.  * 等待无效 entries 进入 ReferenceQueue 以便下一次调用 getTable 时被清理  
  15.  */  
  16. Thread.sleep(1000);   
  17.    
  18. /**  
  19.  * 一旦没有指向 key 的强引用, WeakHashMap 在 GC 后将自动删除相关的 entry  
  20.  */  
  21. assertFalse(weakHashMap.containsValue(value));   
  22. }   
  23.  

3. SoftReference

SoftReference 于 WeakReference 的特性基本一致, 最大的区别在于 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用

  
  
  1. @Test  
  2. public void softReference() {   
  3. Object referent = new Object();   
  4. SoftReference<Object> softRerference = new SoftReference<Object>(referent);   
  5.  
  6. assertNotNull(softRerference.get());   
  7.    
  8. referent = null;   
  9. System.gc();   
  10.    
  11. /**  
  12.  *soft references 只有在 jvm OutOfMemory 之前才会被回收, 所以它非常适合缓存应用  
  13.  */  
  14. assertNotNull(softRerference.get());   
  15. }   
  16.  
  17.  

4. PhantomReference

作为本文主角, Phantom Reference(幽灵引用) 与 WeakReference 和 SoftReference 有很大的不同,因为它的 get() 方法永远返回 null, 这也正是它名字的由来

  
  
  1. @Test  
  2. public void phantomReferenceAlwaysNull() {   
  3. Object referent = new Object();   
  4. PhantomReference<Object> phantomReference = new PhantomReference<Object>(referent, new ReferenceQueue<Object>());   
  5.    
  6. /**  
  7.  * phantom reference 的 get 方法永远返回 null   
  8.  */  
  9. assertNull(phantomReference.get());   
  10. }   
  11.  
  12.  

诸位可能要问, 一个永远返回 null 的 reference 要来何用,请注意构造 PhantomReference 时的第二个参数 ReferenceQueue(事实上 WeakReference & SoftReference 也可以有这个参数),
PhantomReference 唯一的用处就是跟踪 referent何时被 enqueue 到 ReferenceQueue 中.

5. RererenceQueue

当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收, 这时可以做一些合适的清理工作. 将一个 ReferenceQueue 传给一个 Reference 的构造函数, 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中, WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.

  
  
  1. @Test  
  2. public void referenceQueue() throws InterruptedException {   
  3. Object referent = new Object();  
  4. ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();   
  5. WeakReference<Object> weakReference = new WeakReference<Object>(referent, referenceQueue);   
  6.    
  7. assertFalse(weakReference.isEnqueued());   
  8. Reference<? extends Object> polled = referenceQueue.poll();   
  9. assertNull(polled);   
  10.    
  11. referent = null;   
  12. System.gc();   
  13.  
  14. assertTrue(weakReference.isEnqueued());   
  15. Reference<? extends Object> removed = referenceQueue.remove();   
  16. assertNotNull(removed);   
  17. }  
  18.  


6.PhantomReferencevs WeakReference

PhantomReference有两个好处, 其一, 它可以让我们准确地知道对象何时被从内存中删除, 这个特性可以被用于一些特殊的需求中(例如 Distributed GC,XWork 和 google-guice 中也使用 PhantomReference 做了一些清理性工作).

其二, 它可以避免 finalization 带来的一些根本性问题, 上文提到 PhantomReference 的唯一作用就是跟踪 referent 何时被 enqueue 到 ReferenceQueue 中,但是 WeakReference 也有对应的功能, 两者的区别到底在哪呢 ?
这就要说到 Object 的 finalize 方法, 此方法将在 gc 执行前被调用, 如果某个对象重载了 finalize 方法并故意在方法内创建本身的强引用,这将导致这一轮的 GC 无法回收这个对象并有可能
引起任意次 GC, 最后的结果就是明明 JVM 内有很多 Garbage 却 OutOfMemory, 使用 PhantomReference 就可以避免这个问题, 因为 PhantomReference 是在 finalize 方法执行后回收的,也就意味着此时已经不可能拿到原来的引用,也就不会出现上述问题,当然这是一个很极端的例子, 一般不会出现.

7. 对比

Soft vs Weak vs Phantom References
Type Purpose Use When GCed Implementing Class
Strong Reference An ordinary reference. Keeps objects alive as long as they are referenced. normal reference. Any object not pointed to can be reclaimed. default
Soft Reference Keeps objects alive provided there’s enough memory. to keep objects alive even after clients have removed their references (memory-sensitive caches), in case clients start asking for them again by key. After a first gc pass, the JVM decides it still needs to reclaim more space. java.lang.ref.SoftReference
Weak Reference Keeps objects alive only while they’re in use (reachable) by clients. Containers that automatically delete objects no longer in use. After gc determines the object is only weakly reachable java.lang.ref.WeakReference 
java.util.WeakHashMap
Phantom Reference Lets you clean up after finalization but before the space is reclaimed (replaces or augments the use offinalize()) Special clean up processing After finalization. java.lang.ref.PhantomReference

GC、 Reference 与 ReferenceQueue 的交互

A、   GC无法删除存在强引用的对象的内存。
B、   GC发现一个只有软引用的对象内存,那么:
①   SoftReference对象的 referent  域被设置为 null ,从而使该对象不再引用 heap 对象。
②   SoftReference引用过的 heap 对象被声明为 finalizable 
③   当 heap  对象的  finalize()  方法被运行而且该对象占用的内存被释放, SoftReference  对象就被添加到它的  ReferenceQueue (如果后者存在的话)。
C、   GC发现一个只有弱引用的对象内存,那么:
①   WeakReference对象的 referent 域被设置为 null  ,  从而使该对象不再引用heap 对象。
②   WeakReference引用过的 heap 对象被声明为 finalizable 。
③   当heap 对象的 finalize() 方法被运行而且该对象占用的内存被释放时, WeakReference 对象就被添加到它的 ReferenceQueue (如果后者存在的话)。
D、   GC发现一个只有虚引用的对象内存,那么:
①   PhantomReference引用过的 heap 对象被声明为 finalizable 。
②   PhantomReference在堆对象被释放之前就被添加到它的 ReferenceQueue 。
 
值得注意的地方有以下几点:
1、 GC 在一般情况下不会发现软引用的内存对象,只有在内存明显不足的时候才会发现并释放软引用对象的内存。
2、 GC 对弱引用的发现和释放也不是立即的,有时需要重复几次 GC ,才会发现并释放弱引用的内存对象。
3、软引用和弱引用在添加到 ReferenceQueue 的时候,其指向真实内存的引用已经被置为空了,相关的内存也已经被释放掉了。而虚引用在添加到 ReferenceQueue 的时候,内存还没有释放,仍然可以对其进行访问。
代码示例
通过以上的介绍,相信您对Java 的引用机制以及几种引用方式的异同已经有了一定了解。光是概念,可能过于抽象,下面我们通过一个例子来演示如何在代码中使用 Reference 机制。
  
  
  1. String str  =   new  String( " hello " );  // ①   
  2. ReferenceQueue < String >  rq  =   new  ReferenceQueue < String > ();  // ②   
  3. WeakReference < String >  wf  =   new  WeakReference < String > (str, rq);  // ③   
  4. str = null ;  // ④取消"hello"对象的强引用   
  5. String str1 = wf.get();  // ⑤假如"hello"对象没有被回收,str1引用"hello"对象  
  6. // 假如"hello"对象没有被回收,rq.poll()返回null   
  7. Reference <?   extends  String >  ref = rq.poll();  // ⑥  

在以上代码中,注意⑤⑥两处地方。假如“hello ”对象没有被回收 wf.get() 将返回“ hello ”字符串对象, rq.poll() 返回 null ;而加入“ hello ”对象已经被回收了,那么 wf.get() 返回 null , rq.poll() 返回 Reference 对象,但是此 Reference 对象中已经没有 str 对象的引用了 ( PhantomReference 则与WeakReference 、 SoftReference 不同 )。

引用机制与复杂数据结构的联合应用

了解了GC 机制、引用机制,并配合上 ReferenceQueue ,我们就可以实现一些防止内存溢出的复杂数据类型。

例如,SoftReference 具有构建 Cache 系统的特质,因此我们可以结合哈希表实现一个简单的缓存系统。这样既能保证能够尽可能多的缓存信息,又可以保证 Java 虚拟机不会因为内存泄露而抛出 OutOfMemoryError 。这种缓存机制特别适合于内存对象生命周期长,且生成内存对象的耗时比较长的情况,例如缓存列表封面图片等。对于一些生命周期较长,但是生成内存对象开销不大的情况,使用WeakReference 能够达到更好的内存管理的效果。

SoftHashmap 的源码一份,相信看过之后,大家会对 Reference 机制的应用有更深入的理解。

  
  
  1. package  com. *** .widget;  
  2.     // : SoftHashMap.java    
  3.     import  java.util. * ;   
  4.     import  java.lang.ref. * ;   
  5.     import  android.util.Log;  
  6.      
  7.     public   class  SoftHashMap  extends  AbstractMap  {   
  8.       /**  The internal HashMap that will hold the SoftReference.  */    
  9.       private   final  Map hash  =   new  HashMap();   
  10.       /**  The number of "hard" references to hold internally.  */    
  11.       private   final   int  HARD_SIZE;   
  12.       /**  The FIFO list of hard references, order of last access.  */    
  13.       private   final  LinkedList hardCache  =   new  LinkedList();   
  14.       /**  Reference queue for cleared SoftReference objects.  */    
  15.       private  ReferenceQueue queue  =   new  ReferenceQueue();   
  16.       // Strong Reference number   
  17.       public  SoftHashMap()  {  this ( 100 ); }    
  18.       public  SoftHashMap( int  hardSize)  { HARD_SIZE  =  hardSize; }    
  19.        
  20.       public  Object get(Object key)  {   
  21.        Object result  =   null ;   
  22.         //  We get the SoftReference represented by that key    
  23.        SoftReference soft_ref  =  (SoftReference)hash.get(key);   
  24.         if  (soft_ref  !=   null )  {   
  25.           //  From the SoftReference we get the value, which can be   
  26.           //  null if it was not in the map, or it was removed in   
  27.           //  the processQueue() method defined below    
  28.         result  =  soft_ref.get();   
  29.           if  (result  ==   null )  {   
  30.             //  If the value has been garbage collected, remove the   
  31.             //  entry from the HashMap.    
  32.            hash.remove(key);   
  33.          }   else   {   
  34.             //  We now add this object to the beginning of the hard   
  35.             //  reference queue.  One reference can occur more than   
  36.             //  once, because lookups of the FIFO queue are slow, so   
  37.             //  we don't want to search through it each time to remove   
  38.             //  duplicates.   
  39.               // keep recent use object in memory   
  40.            hardCache.addFirst(result);   
  41.             if  (hardCache.size()  >  HARD_SIZE)  {   
  42.               //  Remove the last entry if list longer than HARD_SIZE    
  43.              hardCache.removeLast();   
  44.            }    
  45.          }    
  46.        }    
  47.         return  result;   
  48.      }    
  49.     
  50.       /**  We define our own subclass of SoftReference which contains   
  51.       not only the value but also the key to make it easier to find   
  52.       the entry in the HashMap after it's been garbage collected.  */    
  53.       private   static   class  SoftValue  extends  SoftReference  {   
  54.         private   final  Object key;  //  always make data member final    
  55.         /**  Did you know that an outer class can access private data   
  56.         members and methods of an inner class?  I didn't know that!   
  57.         I thought it was only the inner class who could access the   
  58.         outer class's private information.  An outer class can also   
  59.         access private members of an inner class inside its inner   
  60.         class.  */    
  61.         private  SoftValue(Object k, Object key, ReferenceQueue q)  {   
  62.           super (k, q);   
  63.           this .key  =  key;   
  64.        }    
  65.      }    
  66.     
  67.       /**  Here we go through the ReferenceQueue and remove garbage   
  68.       collected SoftValue objects from the HashMap by looking them   
  69.       up using the SoftValue.key data member.  */    
  70.       public   void  processQueue()  {   
  71.        SoftValue sv;   
  72.         while  ((sv  =  (SoftValue)queue.poll())  !=   null )  {   
  73.             if (sv.get() ==   null ) {  
  74.                Log.e( " processQueue " ,  " null " );  
  75.            } else {  
  76.                Log.e( " processQueue " ,  " Not null " );  
  77.            }   
  78.          hash.remove(sv.key);  //  we can access private data!   
  79.          Log.e( " SoftHashMap " ,  " release  "   +  sv.key);  
  80.        }    
  81.      }    
  82.       /**  Here we put the key, value pair into the HashMap using   
  83.       a SoftValue object.  */    
  84.       public  Object put(Object key, Object value)  {   
  85.        processQueue();  //  throw out garbage collected values first    
  86.        Log.e( " SoftHashMap " ,  " put into  "   +  key);  
  87.         return  hash.put(key,  new  SoftValue(value, key, queue));   
  88.      }    
  89.       public  Object remove(Object key)  {   
  90.        processQueue();  //  throw out garbage collected values first    
  91.         return  hash.remove(key);   
  92.      }    
  93.       public   void  clear()  {   
  94.        hardCache.clear();   
  95.        processQueue();  //  throw out garbage collected values    
  96.       hash.clear();   
  97.     }    
  98.      public   int  size()  {   
  99.       processQueue();  //  throw out garbage collected values first    
  100.        return  hash.size();   
  101.     }    
  102.      public  Set entrySet()  {   
  103.        //  no, no, you may NOT do that!!! GRRR    
  104.        throw   new  UnsupportedOperationException();   
  105.     }   
  106.   }  


 Java GC小结
 一般的应用程序不会涉及到 Reference 编程, 但是了解这些知识会对理解Java GC 的工作原理以及性能调优有一定帮助, 在实现一些基础性设施比如缓存时也可能会用到, 希望本文能有所帮助.

原文链接:http://henryyang.iteye.com/blog/1188328


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值