天天看點

一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

開講前,我們先回顧下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):方法區的一部分,存放編譯器生成的各種字面值和符号引用。
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

圖1 java 虛拟機運作時資料區

一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

本文即将介紹到的:Java的強引用、軟引用、弱引用、虛引用,都與JVM的GC有着莫大的關系。

我們都知道,常用的GC回收關注的JVM區域為是Java堆(包含了方法區{外界也稱為“常量池”}),而我們的引用(無論是強/軟/弱/虛)都是指向于堆的某一塊記憶體區域。Java堆與方法區,隻有在程式運作期間才知道會建立哪些對象,這部分記憶體的配置設定和回收都是動态的。

主流商用程式語言的主流實作中,都是通過可達性分析(Reachability Analysis)來判定對象是否存活。

可達性算法基本思路是:通過一系列的稱為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜素走過的路徑稱為“Reference Chain”(引用鍊),當下一個對象到GC Roots沒有任何引用鍊相連時,則證明該對象不可用。

一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

圖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           
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用

—END—

一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用
一文帶你讀懂Java的強引用、軟引用、弱引用、虛引用