1. 程式人生 > >Java強、軟、弱和虛引用及GC Root——記憶體優化(一)

Java強、軟、弱和虛引用及GC Root——記憶體優化(一)

你也可以檢視我的其他同類文章,也會讓你有一定的收貨!

記憶體優化

記憶體優化的兩個主要方向:

  1. 記憶體洩露:已經沒有使用的物件,GC Root 還對其保持強引用,導致GC無法回收。
  2. 記憶體抖動:頻繁的建立物件,導致 GC 頻率較高,導致應用的卡頓

GC Roots

我們常說的垃圾回收機制中會提到GC Roots這個詞,也就是Java虛擬機器中所有引用的根物件。我們都知道,垃圾回收器不會回收GC Roots以及那些被它們間接強引用的物件。但是,對於GC Roots的定義卻不是很清楚。它們都包括哪些物件呢?

經過查閱,瞭解JVM中GC Roots的大致分類,然後用自己的語言解釋一下:

  • Class 由System Class Loader/Boot Class Loader載入的類物件,這些物件不會被回收。需要注意的是其它的Class Loader例項載入的類物件不一定是GC root,除非這個類物件恰好是其它形式的GC root;

  • Thread 執行緒,啟用狀態的執行緒;

  • Stack Local 棧中的物件。每個執行緒都會分配一個棧,棧中的區域性變數或者引數都是GC root,因為它們的引用隨時可能被用到;

  • JNI Local JNI中的區域性變數和引數引用的物件;可能在JNI中定義的,也可能在虛擬機器中定義

  • JNI Global JNI中的全域性變數引用的物件;同上

  • Monitor Used 用於保證同步的物件,例如wait(),notify()中使用的物件、鎖等。

  • Held by JVM JVM持有的物件。JVM為了特殊用途保留的物件,它與JVM的具體實現有關。比如有System Class Loader, 一些Exceptions物件,和一些其它的Class Loader。對於這些類,JVM也沒有過多的資訊。

參考:

引用

在JDK1.2以前的版本中,當一個物件不被任何變數引用,那麼程式就無法再使用這個物件。也就是說,只有物件處於可觸及狀態,程式才能使用它。

這就像在日常生活中,從商店購買了某樣物品後,如果有用,就一直保留它,否則就把它扔到垃圾箱,由清潔工人收走。一般說來,如果物品已經被扔到垃圾箱,想再 把它撿回來使用就不可能了。

從JDK1.2版本開始,把物件的引用分為四種級別,從而使程式能更加靈活的控制物件的生命週期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用

1.強引用
如果一個物件具有強引用,那就類似於必不可少的生活用品,

  • 垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。

2.軟引用(SoftReference)
如果一個物件只具有軟引用

  • 記憶體空間足夠,垃圾回收器就不會回收它,
  • 記憶體空間不足了,就會回收這些物件的記憶體。

只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。

軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。

3.弱引用(WeakReference)
如果一個物件只具有弱引用,

  • 在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。

弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。

弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。

4.虛引用(PhantomReference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。
如果一個物件僅持有虛引用,

  • 它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。

虛引用主要用來跟蹤物件被垃圾回收的活動。

虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列(ReferenceQueue)聯合使用。

當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。這句感覺不得體,和引用佇列使用,不管垃圾回收器是否準備回收,只要引用了物件,就會被加入引用佇列中。

程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。

引用佇列的作用:聯合引用佇列使用,在引用了一個物件後,會把物件新增到引用佇列中,程式如果發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。

舉個栗子:

//建立一個強引用
String str = new String("hello");

//建立引用佇列, <String>為範型標記,表明佇列中存放String物件的引用
ReferenceQueue<String> rq = new ReferenceQueue<String>();

//建立一個弱引用,它引用"hello"物件,並且與rq引用佇列關聯
//<String>為範型標記,表明WeakReference會弱引用String物件
WeakReference<String> wf = new WeakReference<String>(str, rq);

以上程式程式碼執行完畢,記憶體中引用與物件的關係如圖11-10所示。

這裡寫圖片描述

圖11-10 “hello”物件同時具有強引用和弱引用

在圖11-10中,帶實線的箭頭表示強引用,帶虛線的箭頭表示弱引用。從圖中可以看出,此時”hello”物件被str強引用,並且被一個WeakReference物件弱引用,因此”hello”物件不會被垃圾回收。

在以下程式程式碼中,

  • 把引用”hello”物件的str變數置為null,
  • 再通過WeakReference弱引用的get()方法獲得”hello”物件的引用:

假如”hello”物件沒有被垃圾回收:

  1. 接下來在第⑤行執行wf.get()方法會返回 “hello”物件的引用,並且使得這個物件被str1強引用。
  2. 在第⑥行執行rq.poll()方法會返回null,因為此時引用佇列中沒有任何引用。ReferenceQueue的poll()方法用於返回佇列中的引用,如果沒有則返回null。
String str = new String("hello"); //① 
ReferenceQueue<String> rq = new ReferenceQueue<String>(); //② 
WeakReference<String> wf = new WeakReference<String>(str, rq); //③

str=null; //④取消"hello"物件的強引用
String str1=wf.get(); //⑤假如"hello"物件沒有被回收,str1引用"hello"物件

//假如"hello"物件沒有被回收,rq.poll()返回null
Reference<? extends String> ref=rq.poll(); //⑥

這裡寫圖片描述
圖11-11 “hello”物件只具有弱引用

執行完以上第④行後,記憶體中引用與物件的關係如圖11-11所示

此 時”hello”物件僅僅具有弱引用,因此它有可能被垃圾回收。

假如”hello”物件被回收

  1. WeakReference物件的引用被加入到ReferenceQueue中,
  2. 接下來wf.get()方法返回null,並且rq.poll()方法返回WeakReference物件的引用。
String str = new String("hello"); //①
ReferenceQueue<String> rq = new ReferenceQueue<String>(); //② 
WeakReference<String> wf = new WeakReference<String>(str, rq); //③
str=null; //④

//兩次催促垃圾回收器工作,提高"hello"物件被回收的可能性
System.gc(); //⑤
System.gc(); //⑥
String str1=wf.get(); //⑦ 假如"hello"物件被回收,str1為null
Reference<? extends String> ref=rq.poll(); //⑧

圖11-12顯示了執行完第⑧行後記憶體 中引用與物件的關係。
這裡寫圖片描述

圖11-12 “hello”物件被垃圾回收,弱引用被加入到引用佇列

在以上程式程式碼中,執行完第④行後,”hello”物件僅僅具有弱引用。接下來兩次呼叫System.gc()方法,催促垃圾回收器工作,從而提高 “hello”物件被回收的可能性。

關注我的公眾號,輕鬆瞭解和學習更多技術
這裡寫圖片描述