開講前,我們先回顧下JVM的基本結構。根據《Java虛拟機規範(Java SE 7版)》。Java虛拟機所管理的記憶體将會包括以下幾個運作時資料區域:
- 程式計數器(Program Counter Register):目前線程執行的位元組碼訓示器
- Java虛拟機棧(Java Virtual Machine Stacks):Java方法執行的記憶體模型,每個方法會建立一個棧幀用于存儲局部變量表、操作數棧、動态連結、方法出口等資訊。
- 本地方法棧(Native Method Stack):(虛拟機使用到的)本地方法執行的記憶體模型。
- Java堆(Java Heap):虛拟機啟動時建立的記憶體區域,唯一目的是存放對象執行個體,處于邏輯連續但實體不連續記憶體空間中。
- 方法區(Method Area):存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。
- 運作時常量池(Runtime Constant Pool):方法區的一部分,存放編譯器生成的各種字面值和符号引用。
圖1 java 虛拟機運作時資料區
本文即将介紹到的:Java的強引用、軟引用、弱引用、虛引用,都與JVM的GC有着莫大的關系。
我們都知道,常用的GC回收關注的JVM區域為是Java堆(包含了方法區{外界也稱為“常量池”}),而我們的引用(無論是強/軟/弱/虛)都是指向于堆的某一塊記憶體區域。Java堆與方法區,隻有在程式運作期間才知道會建立哪些對象,這部分記憶體的配置設定和回收都是動态的。
主流商用程式語言的主流實作中,都是通過可達性分析(Reachability Analysis)來判定對象是否存活。
可達性算法基本思路是:通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜素走過的路徑稱為“Reference Chain”(引用鍊),當下一個對象到GC Roots沒有任何引用鍊相連時,則證明該對象不可用。
圖2 可達性分析算法判斷對象是否存活
進入今天的主題:再談引用是什麼。無論是何種GC回收算法,判斷對象的存活都與“引用”有關。在SDK1.2之前,Java對引用定義很傳統:如果reference類型的資料存儲的數值代表的是另外一塊記憶體的起始位址,就稱為“這塊記憶體代表着一個引用”。這種定義比較雖純粹但狹隘,對于如何描述一類“食之無味,棄之可惜”的對象顯得無能為力。我們後來希望描述一類對象:當記憶體空間足夠時,能夠保留在記憶體之中;如果記憶體空間在GC回收後還是非常緊張,則可以抛棄這些對象。很多系統的緩存功能都符合這樣的應用場景。
于是,在JDK1.2之後,Java對引用概念進行了擴充,把引用分類為:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,它們的引用強度一次逐漸減弱。
強引用(Strong Reference)
在程式代碼中普遍存在的,類似下面這類的引用,隻要強引用存在,那麼GC Collector就永遠不會回收掉被引用的對象。
Object obj = new Object();
- 例子
/** * <p> * 強引用:當對象指向記憶體為空不關聯時,才會被GC回收,否則永遠不會回收。 * </p> */ public class ObjectReference { public static void main(String[] args) { String str = "StrongReference"; System.out.println(str); System.gc(); System.out.println(str); str = null; System.out.println(str); } }
- 輸出
StrongReference StrongReference null
軟引用(Soft Reference)
描述一些還有用但并非必需的對象。對于軟引用關聯着的對象,在系統即将要發生記憶體溢出異常之前,将會把這些對象列入回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會抛出記憶體溢出異常。JDK1.2之後,提供了 SoftReference類實作軟引用。
- 例子(加入虛拟機啟動參數:-Xms10m -Xmx40m,表示JVM最大堆記憶體為12M)
/** * <p> * 軟引用:當記憶體不足時,才會觸發JVM進行GC回收。 * 适合做緩存。 * </p> */ public class SoftReferenceObject1 { public static void main(String[] args) { softRefenceTest(); } /** * 僅僅gc, 還不會回收。需記憶體不夠 GC的時候才回收軟引用。 * 啟動參數: * -Xmx16m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:NewSize=2m -XX:MaxNewSize=2m */ private static void softRefenceTest() { final int _8M = 8 * 1024 * 1024; List<SoftReference> list = new ArrayList<SoftReference>(); System.out.println("add 8m -1"); list.add(new SoftReference(new byte[_8M])); System.out.println("add 8m -2"); list.add(new SoftReference(new byte[_8M])); System.out.println("add 8m -3"); list.add(new SoftReference(new byte[_8M])); System.out.println("add 8m -4"); list.add(new SoftReference(new byte[_8M])); System.out.println("add 8m -5"); list.add(new SoftReference(new byte[_8M])); System.out.println("add 8m -6"); list.add(new SoftReference(new byte[_8M])); System.gc(); list.stream().forEach(r-> System.out.println(r.get())); } }
add 8m -1 add 8m -2 add 8m -3 add 8m -4 add 8m -5 add 8m -6 null null null [B@5f4da5c3 [B@443b7951 [B@14514713
- 解析
啟動程式時,設定堆記憶體最大為40M;(-Xms10m -Xmx40m) 而後程式依次申請配置設定8M的數組連續存儲空間, 經過6次建立對象操作,在5次時,已經達到堆記憶體上限40M了,是以觸發了JVM的GC回收, 導緻此前4次建立的軟引用所指向的堆記憶體對象被全部回收; 然後第5/6次建立的對象依舊能夠建立完成, 因為此時記憶體共耗損16M,還沒達到記憶體上限,是以軟引用的對象可以繼續存活。
弱引用(Weak Reference)
描述非必需對象,但他的強度比軟引用更弱一些,被弱引用關聯的對象隻能生存到下一次GC發生之前。當GC發生時,無論記憶體是否足夠,都會回收掉隻被弱引用關聯的對象。JDK1.2之後,提供了 WeakReference類實作軟引用。
/** * <p> * 弱引用:無論記憶體是否足夠,都會被GC回收 * </p> */ public class WeakReferenceObject { public static void main(String[] args) { WeakReference weakReference = new WeakReference(new long[1024*1024]); System.out.println(weakReference.get()); System.gc(); System.out.println(weakReference.get()); } }
[J@312b1dae null
虛引用(Phantom Reference)
幽靈引用或幻影引用,是最弱的一種引用關系。一個對象是否有虛引用,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象執行個體。為一個對象設定虛引用關聯的唯一目的,就是能夠在這個對象被GC回收時,收到一個系統通知。JDK1.2之後,提供了 PhantomReference類實作軟引用。
/** * <p> * 虛引用/幻影引用: * 1)無法通過虛引用擷取對一個對象的真實引用 * 2)虛引用必須通過與 PhantomReference 組合一起使用。當GC 回收一個對象,發現它還有虛引用,就會在回收前把這個引用加到與之關聯的ReferenceQueue中 * NIO運用到它管理堆外存。 * </p> */ public class ReferenceQueueObject { public static void main(String[] args) { ReferenceQueue referenceQueue = new ReferenceQueue(); PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], referenceQueue); System.out.println(reference.get()); } }
null
—END—