【JVM】如何理解強引用、軟引用、弱引用、虛引用?

xd會飛的貓發表於2020-06-01

整體架構

強引用

強引用是預設支援,當記憶體不足的時候,JVM開始垃圾回收,對於強引用的物件,就算是出現了OOM也不會回收物件。

強引用是最常見的普通物件引用,只要還有強引用指向物件,物件就存活,垃圾回收器不會處理存活物件。一般把一個物件賦給一個引用變數,這個引用變數就是強引用。當一個物件被強引用變數所引用,它就處於可達狀態,是不會被垃圾回收的,即使之後都不會再用到了,也不會回收。因此強引用是造成Java記憶體洩漏的主要原因之一

關於Java記憶體洩漏的詳細內容,可以參考這篇部落格:https://blog.csdn.net/m0_38110132/article/details/81986334

對於一個普通物件,如果沒有其他引用關係,只要超過了引用的作用域或者顯式地將相應的強引用賦值為null,一般認為就是可以被垃圾回收了。(具體的回收時機看垃圾回收策略)

下例中,b就是強引用。

1     public static void main(String[] args) {
2         Object a = new Object();
3         Object b = a;
4         a = null;
5         System.out.println(b);//java.lang.Object@4554617c
6     }

軟引用

軟引用是一種相對強引用弱化了一些的引用,用java.lang.ref.SoftReference實現,可以讓物件豁免一些垃圾收集。當系統記憶體充足的時候,不會被回收;當系統記憶體不足的時候,會被回收。

軟引用一般用於對記憶體敏感的程式中,比如快取記憶體。

 1 import java.lang.ref.SoftReference;
 2 
 3 public class SoftReferenceDemo {
 4     public static void main(String[] args) {
 5         Object a = new Object();
 6         SoftReference<Object> softReference = new SoftReference<>(a);//軟引用
 7         //a和軟引用指向同一個物件
 8         System.out.println(a);//java.lang.Object@4554617c
 9         System.out.println(softReference.get());//java.lang.Object@4554617c
10 
11         //記憶體夠用,軟引用不會被回收
12         a = null;
13         System.gc();//記憶體夠用不會自動gc,手動喚醒gc
14         System.out.println(a);//null
15         System.out.println(softReference.get());//java.lang.Object@4554617c
16 
17         //記憶體不夠用時
18         try{
19             //配置Xms和Xmx為5MB
20             byte[] bytes = new byte[1024*1024*30];//設定30MB超記憶體
21         }catch (Throwable e){
22             e.printStackTrace();
23         }finally {
24             System.out.println(a);//null
25             System.out.println(softReference.get());//null
26         }
27     }
28 }

使用場景

一個應用需要讀取大量的本地圖片,如果每次讀取都從硬碟讀取會嚴重影響效能,如果一次性全部載入到記憶體,記憶體可能會溢位。

可以使用軟引用解決這個問題,使用一個HashMap來儲存圖片路徑和圖片物件管理的軟引用之間的對映關係,記憶體不足時,JVM會自動回收快取圖片物件的佔用空間,有效地避免了OOM(Out Of Memory)問題。

Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>

弱引用

弱引用需要用java.lang.ref.WeakReference實現,它比軟引用的生存期更短,對於弱引用的物件來說,只要垃圾回收機制一執行,不管JVM的記憶體空間是否夠,都會回收該物件的佔用記憶體。

 1 import java.lang.ref.WeakReference;
 2 
 3 public class SoftReferenceDemo {
 4     public static void main(String[] args) {
 5         Object a = new Object();
 6         WeakReference<Object> softReference = new WeakReference<>(a);//軟引用
 7         //a和弱引用指向同一個物件
 8         System.out.println(a);//java.lang.Object@4554617c
 9         System.out.println(softReference.get());//java.lang.Object@4554617c
10 
11         //記憶體夠用,弱引用也會被回收
12         a = null;
13         System.gc();//記憶體夠用不會自動gc,手動喚醒gc
14         System.out.println(a);//null
15         System.out.println(softReference.get());//null
16     }
17 }

關於WeakHashMap

 1     public static void weakHashMapTest() {
 2         Integer key = new Integer(1);
 3         String value = "李四";
 4         Map<Integer,String> weakHashMap = new WeakHashMap();
 5         weakHashMap.put(key, value);
 6         System.out.println(weakHashMap);//{1=李四}
 7         key = null;
 8         System.gc();
 9         System.out.println(weakHashMap);//{}
10     }
11 
12     public static void hashMapTest() {
13         HashMap<Integer,String> map = new HashMap<>();
14         Integer key = 1;
15         String value = "張三";
16         map.put(key,value);
17         System.out.println(map);//{1=張三}
18         key = null;
19         System.gc();
20         System.out.println(map);//{1=張三}
21     }

在HashMap中,鍵被置為null,喚醒gc後,不會垃圾回收鍵為null的鍵值對。但是在WeakHashMap中,鍵被置為null,喚醒gc後,鍵為null的鍵值對會被回收。

虛引用

虛引用要通過java.lang.ref.PhantomReference類來實現,虛引用不會決定物件的生命週期,如果一個物件只有虛引用,就相當於沒有引用,在任何時候都可能會被垃圾回收器回收。它不能單獨使用也不能訪問物件,虛引用必須和引用佇列聯合使用

虛引用的主要作用是跟蹤物件被垃圾回收的狀態,僅僅是提供一種確保物件被finalize以後,做某些事情的機制。

PhantomReference的get方法總是返回null,因此無法訪問對應的引用物件,設定虛引用關聯唯一的目的是在物件被收集器回收的時候收到一個系統通知,或者後續新增進一步的處理。Java允許使用finalize()方法在垃圾回收器將物件從記憶體中清理出去之前做一些必要的清理工作。【例如實現一個監控物件的通知機制】

引用佇列

WeakReference和ReferenceQueue的聯合使用效果:

 1     public static void weakReferenceTest() {
 2         Object a = new Object();
 3         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 4         WeakReference<Object> weakReference = new WeakReference<>(a,queue);
 5         System.out.println(a);//java.lang.Object@4554617c
 6         System.out.println(weakReference.get());//java.lang.Object@4554617c
 7         System.out.println(queue.poll());//null
 8         System.out.println("-------------------");
 9         a = null;
10         System.gc();
11         System.out.println(a);//null
12         System.out.println(weakReference.get());//null
13         //虛引用在回收之前被加入到了引用佇列中
14         System.out.println(queue.poll());//java.lang.ref.WeakReference@74a14482
15     }

PhantomReference和ReferenceQueue的聯合使用效果:

 1     public static void phantomReferenceTest() {
 2         Object a = new Object();
 3         ReferenceQueue<Object> queue = new ReferenceQueue<>();
 4         PhantomReference<Object> phantomReference = new PhantomReference<>(a,queue);
 5         System.out.println(a);//java.lang.Object@4554617c
 6         System.out.println(phantomReference.get());//null
 7         System.out.println(queue.poll());//null
 8         System.out.println("-------------------");
 9         a = null;
10         System.gc();
11         System.out.println(a);//null
12         System.out.println(phantomReference.get());//null
13         //引用在回收之前被加入到了引用佇列中
14         System.out.println(queue.poll());//java.lang.ref.WeakReference@74a14482
15     }

總結

強引用:不回收。

軟引用:記憶體不夠就回收。

弱引用:一定回收。

虛引用:一定回收,get出來就是null,引用形同虛設,主要和引用佇列聯合使用,在finalize之前會被放到引用佇列中。

與根物件沒有引用關係的:引用不可達,一定回收。

相關文章