在介紹各類引用以前,先簡單介紹下垃圾回收java
Java中的垃圾回收是根據可達性分析算法來判斷對象是否存活的算法
在主流的商用程序語言(Java、C#,甚至包括前面提到的古老的Lisp)的主流實現中,都是稱經過可達性分析(Reachability Analysis)來斷定對象是否存活的。這個算法的基本思路就是經過一系列的稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來講,就是從GC Roots到這個對象不可達)時,則證實此對象是不可用的。如圖3-1所示,對象object 五、object 六、object 7雖然互相有關聯,可是它們到GC Roots是不可達的,因此它們將會被斷定爲是可回收的對象。spring
在Java語言中,可做爲GC Roots的對象包括下面幾種:緩存
對象是否存活與「引用」有關。框架
在JDK 1.2之前,Java中的引用的定義很傳統:若是reference類型的數據中存儲的數值表明的是另一塊內存的起始地址,就稱這塊內存表明着一個引用。這種定義很純粹,可是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態,對於如何描述一些「食之無味,棄之惋惜」的對象就顯得無能爲力(這裏說的就是強引用,最基本的引用方式)。函數
咱們但願能描述這樣一類對象:當內存空間還足夠時,則能保留在內存之中;若是內存空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這樣的應用場景。測試
在JDK 1.2以後,Java對引用的概念進行了擴充,將引用分爲強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱。this
引用類型 | GC策略 | 簡介 |
---|---|---|
強引用(Strong Reference) | 永遠不會回收(GC ROOT可引用到的前提下) | 最基本的引用Object obj=new Object() |
軟引用(Soft Reference) | OOM以前回收 | SoftReference |
弱引用(Weak Reference) | 下一次GC前 | WeakReference |
虛引用(Phantom Reference) | 未知,也就是隨時可能被回收 | PhantomReference |
強引用就是最基本的引用方式,Object obj=new Object()
,引用的是另外一塊內存的起始地址。強引用的對象回收基於「可達性分析」算法,當對象不可達時纔可能會被回收。spa
好比方法中new的對象,引用賦值給方法內的局部變量(局部變量存儲在棧幀中的局部變量表),當方法結束以後,棧幀出棧,對象就天然不可達了,不可達就可能會被回收線程
軟引用是用來描述一些還有用但並不是必需的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。在JDK
1.2以後,提供了SoftReference類來實現軟引用。
SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj);
寫個例子來測試下軟引用的GC策略:
# JVM OPTIONS: -XX:+PrintGCDetails -Xmx5m public class ReferenceTest { private List<RefObj> refObjs = new ArrayList<>(); private SoftReference<RefObj> ref = new SoftReference<RefObj>(createRefObj(4096*256));//1m public void add(){ refObjs.add(createRefObj(4096)); } private RefObj createRefObj(int dataSize){ RefObj refObj = new RefObj(); byte[] data = new byte[dataSize]; for (int i = 0; i < dataSize; i++) { data[i] = Byte.MAX_VALUE; } refObj.setData(data); return refObj; } public void validRef(){ System.out.println(ref.get()); } public static void main(String[] args) { ReferenceTest referenceTest = new ReferenceTest(); for (int i = 0; i < 1200; i++) { //不停新增堆大小 referenceTest.add(); //新增後查看SoftReference中的對象是否被回收 referenceTest.validRef(); } } private class RefObj{ private byte[] data; public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; } } }
ReferenceTest中維護一個RefObjList和一個SoftReference,往RefObjList不斷添加對象,增長堆大小,直至內存溢出。來觀察下SoftReference中引用的對象是否還存在
測試結果:
# 截取一段關鍵部分 [Full GC (Ergonomics) [PSYoungGen: 1023K->1021K(1536K)] [ParOldGen: 4073K->4073K(4096K)] 5097K->5094K(5632K), [Metaspace: 3534K->3534K(1056768K)], 0.0017581 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] reference.ReferenceTest$RefObj@58372a00 reference.ReferenceTest$RefObj@58372a00 reference.ReferenceTest$RefObj@58372a00 reference.ReferenceTest$RefObj@58372a00 reference.ReferenceTest$RefObj@58372a00 [Full GC (Ergonomics) [PSYoungGen: 1024K->1021K(1536K)] [ParOldGen: 4093K->4093K(4096K)] 5117K->5114K(5632K), [Metaspace: 3534K->3534K(1056768K)], 0.0014771 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 1021K->0K(1536K)] [ParOldGen: 4093K->4072K(4096K)] 5114K->4072K(5632K), [Metaspace: 3534K->3534K(1056768K)], 0.0060554 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] null .....省略 [Full GC (Allocation Failure) [PSYoungGen: 1022K->1022K(1536K)] [ParOldGen: 4093K->4093K(4096K)] 5116K->5116K(5632K), [Metaspace: 3534K->3534K(1056768K)], 0.0014051 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Exception in thread "main" [Full GC (Ergonomics) [PSYoungGen: 1024K->0K(1536K)] [ParOldGen: 4094K->981K(4096K)] 5118K->981K(5632K), [Metaspace: 3538K->3538K(1056768K)], 0.0037282 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] java.lang.OutOfMemoryError: Java heap space at reference.ReferenceTest.createRefObj(ReferenceTest.java:21) at reference.ReferenceTest.add(ReferenceTest.java:16) at reference.ReferenceTest.main(ReferenceTest.java:35) Heap PSYoungGen total 1536K, used 39K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000) eden space 1024K, 3% used [0x00000000ffe00000,0x00000000ffe09e10,0x00000000fff00000) from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) ParOldGen total 4096K, used 981K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000) object space 4096K, 23% used [0x00000000ffa00000,0x00000000ffaf55f0,0x00000000ffe00000) Metaspace used 3565K, capacity 4564K, committed 4864K, reserved 1056768K class space used 384K, capacity 388K, committed 512K, reserved 1048576K
從程序+GC日誌中能夠看出,在某次GC後(OOM前的GC),SoftReference中引用的對象獲取不到了,已經被GC回收。
弱引用也是用來描述非必需對象的,可是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK
1.2以後,提供了WeakReference類來實現弱引用。
弱引用和軟引用使用方式一致,只是對應的類不一樣,和GC策略不一樣而已。
WeakReference<RefObj> ref = new WeakReference<RefObj>(refObj);
基於上面軟引用的測試代碼稍做修改,來測試下弱引用的GC策略:
public class ReferenceTest { private List<RefObj> refObjs = new ArrayList<>(); private WeakReference<RefObj> ref = new WeakReference<RefObj>(createRefObj(4096*256));//1m public void add(){ refObjs.add(createRefObj(4096)); } private RefObj createRefObj(int dataSize){ RefObj refObj = new RefObj(); byte[] data = new byte[dataSize]; for (int i = 0; i < dataSize; i++) { data[i] = Byte.MAX_VALUE; } refObj.setData(data); return refObj; } public void validRef(){ System.out.println(ref.get()); } public static void main(String[] args) { ReferenceTest referenceTest = new ReferenceTest(); referenceTest.validRef(); referenceTest.add(); //手動GC後查看WeakReference中的對象是否還存在 System.gc(); referenceTest.validRef(); } private class RefObj{ private byte[] data; public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; } } }
測試結果:
[GC (Allocation Failure) [PSYoungGen: 1526K->512K(1536K)] 1992K->1266K(5632K), 0.0005932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] reference.ReferenceTest$RefObj@58372a00 [GC (System.gc()) [PSYoungGen: 706K->512K(1536K)] 2484K->2346K(5632K), 0.0005772 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 512K->0K(1536K)] [ParOldGen: 1834K->972K(4096K)] 2346K->972K(5632K), [Metaspace: 3493K->3493K(1056768K)], 0.0062458 secs] [Times: user=0.16 sys=0.00, real=0.01 secs] null Heap PSYoungGen total 1536K, used 31K [0x00000000ffe00000, 0x0000000100000000, 0x0000000100000000) eden space 1024K, 3% used [0x00000000ffe00000,0x00000000ffe07cc8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 4096K, used 972K [0x00000000ffa00000, 0x00000000ffe00000, 0x00000000ffe00000) object space 4096K, 23% used [0x00000000ffa00000,0x00000000ffaf31d8,0x00000000ffe00000) Metaspace used 3500K, capacity 4500K, committed 4864K, reserved 1056768K class space used 381K, capacity 388K, committed 512K, reserved 1048576K
從日誌上看,在任何一次GC(包括手動GC)以後,GC會回收WeakReference中的對象,不管當前內存是否足夠。
虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。在JDK
1.2以後,提供了PhantomReference類來實現虛引用。
也就是說,虛引用的對象,隨時都有可能被回收
虛引用的用法和軟引用/弱引用也很相似,只是在構造的時候,須要指定一個隊列
//引用隊列,當引用的對象被回收後,Reference對象自己會被添加到referenceQueue中,至關於獲得了一個通知 //軟引用/弱引用中都有此構造參數,只是在虛引用中此參數變成必傳了而已 ReferenceQueue<RefObj> referenceQueue = new ReferenceQueue<>(); PhantomReference<RefObj> ref = new PhantomReference<RefObj>(refObj,referenceQueue); SoftReference<RefObj> ref = new SoftReference<RefObj>(refObj,referenceQueue); WeakReference<RefObj> ref = new WeakReference<RefObj>(refObj,referenceQueue);
仍是基於上面的測試代碼稍做修改,來測試一下虛引用以及回收隊列
public class ReferenceTest { private List<RefObj> refObjs = new ArrayList<>(); private ReferenceQueue<RefObj> referenceQueue = new ReferenceQueue<>(); private PhantomReference<RefObj> ref = new PhantomReference<RefObj>(createRefObj(4096*256),referenceQueue);//1m public void add(){ refObjs.add(createRefObj(4096)); } /** * 啓一個子線程,監控回收的引用隊列 */ public void referenceQueueMonitor(){ new Thread(()->{ try{ Reference<RefObj> weakReference; while ((weakReference = (Reference<RefObj>) referenceQueue.remove())!=null){ System.out.println("collect "+weakReference); } }catch (InterruptedException e){} }).start(); } private RefObj createRefObj(int dataSize){ RefObj refObj = new RefObj(); byte[] data = new byte[dataSize]; for (int i = 0; i < dataSize; i++) { data[i] = Byte.MAX_VALUE; } refObj.setData(data); return refObj; } public void validRef(){ System.out.println(ref.get()); } public static void main(String[] args) { ReferenceTest referenceTest = new ReferenceTest(); referenceTest.referenceQueueMonitor(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } referenceTest.validRef(); referenceTest.add(); System.gc(); referenceTest.validRef(); } private class RefObj{ private byte[] data; public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; } } }
測試結果:
#第一次: [GC (Allocation Failure) [PSYoungGen: 1525K->512K(1536K)] 3466K->2661K(5632K), 0.0005258 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] null [GC (System.gc()) [PSYoungGen: 567K->512K(1536K)] 2717K->2717K(5632K), 0.0005225 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 512K->0K(1536K)] [ParOldGen: 2205K->2213K(4096K)] 2717K->2213K(5632K), [Metaspace: 4379K->4379K(1056768K)], 0.0078481 secs] [Times: user=0.13 sys=0.00, real=0.01 secs] null #最後打印了回收日誌 collect java.lang.ref.PhantomReference@7be2d776 #第N次: [GC (Allocation Failure) [PSYoungGen: 1536K->512K(1536K)] 3440K->2536K(5632K), 0.0005524 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] #先打印了回收日誌 collect java.lang.ref.PhantomReference@30a3ced6 null [GC (System.gc()) [PSYoungGen: 573K->512K(1536K)] 2597K->2580K(5632K), 0.0005956 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 512K->0K(1536K)] [ParOldGen: 2068K->2252K(4096K)] 2580K->2252K(5632K), [Metaspace: 4387K->4387K(1056768K)], 0.0082860 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] null
從日誌上,屢次結果並不一致。因爲程序啓動時會發生GC,中途又手動觸發了GC。結合虛引用的特色來看,虛引用的的對象存活週期並不能肯定,對象可能在任什麼時候候被回收。
咱們但願能描述這樣一類對象:當內存空間還足夠時,則能保留在內存之中;若是內存空間在進行垃圾收集後仍是很是緊張,則能夠拋棄這些對象。不少系統的緩存功能都符合這樣的應用場景。
最容易想到的就是緩存了,內存不足時釋放部分數據,相似Redis/Ehcache之類的淘汰策略。
下面列出一下JDK/框架中的應用場景:
java.util.WeakHashMap
- jdkjava.util.concurrent.ArrayBlockingQueue
- jdkorg.springframework.util.ConcurrentReferenceHashMap
- spring中大量使用了此緩存,包括spring-BeanUtils