引言 在JDK1.2之前Java並沒有提供軟引用、弱引用和虛引用這些高級的引用類型。而是提供了一種基本的引用類型,稱為Reference。並且當時Java中的對象只有兩種狀態:被引用和未被引用。當一個對象被引用時,它將一直存在於記憶體中,直到它不再被任何引用指向時,才會被垃圾回收器回收。而被引用也就是 ...
引言
在JDK1.2之前Java並沒有提供軟引用、弱引用和虛引用這些高級的引用類型。而是提供了一種基本的引用類型,稱為Reference
。並且當時Java中的對象只有兩種狀態:被引用和未被引用。當一個對象被引用時,它將一直存在於記憶體中,直到它不再被任何引用指向時,才會被垃圾回收器回收。而被引用也就是強引用。
而在JDK1.2之後對引用的概念進行了擴充,分為了強引用(StrongReference
)、軟引用(SoftReference
)、弱引用(WeakReference
)和虛引用(PhantomReference
),這4種引用的強度依次減弱。他們的關係如下如:
強引用
強引用是Java中最常見的引用類型。當你創建一個對象並將其賦值給一個變數時,這個變數會持有該對象的強引用。
Order order = new Order(); // 只要order還指向Order對象,那麼Order對象就不會被回收
order = null; // 強引用都被設置為 null 時,不可達,則Order對象被回收
只要存在強引用指向對象,垃圾回收器將永遠不會回收該對象,即使記憶體不足也不會回收。這可能導致記憶體溢出,因為即使記憶體不足,JVM也不會回收強引用對象。當強引用都被設置為null時,對象變成不可達狀態,垃圾回收器會在適當的時候將其回收。
比如以下示例,我們創建一個2M的數組,但是我們設置JVM參數:-Xms2M -Xmx3M
,將JVM的初始記憶體設為2M,最大可用記憶體為3M。
public static void main(String[] args) {
//定義一個2M的數組
byte[] objects = new byte[1024 * 1024 * 2];
}
此時我們執行方法後,發現報錯:
對於強引用,即使記憶體不夠使用,直接報錯OOM,強引用也不會被回收。
對於強引用,就好比生活中,當我們擁有家裡的鑰匙時,我們可以隨時進入你的家,即使我們不需要進入,也能確保我們可以進入。鑰匙是我們進入家的強引用。只有當我們不再擁有鑰匙時,我們才無法進入家,類似於當沒有強引用指向一個對象時,該對象才能被垃圾回收。
軟引用
在JDK1.2之後,用java.lang.ref.SoftReference
類來表示軟引用。軟引用允許對象在記憶體不足時被垃圾回收器回收。如果一個對象只有軟引用指向它,當系統記憶體不足時,垃圾回收器會嘗試回收這些對象來釋放記憶體,如果回收了軟引用對象之後仍然沒有足夠的記憶體,才會拋出記憶體溢出異常。軟引用適用於需要緩存大量對象,但又希望在記憶體不足時釋放部分對象以避免記憶體溢出的情況,用於實現緩存時,當記憶體緊張時,可以釋放部分緩存對象以保證系統的穩定性。
以下示例我們設置JVM參數為:-Xms3M -Xmx5M
,然後連續創建了10個大小為1M的位元組數組,並賦值給了軟引用,然後迴圈遍歷將這些對象列印出來。
private static final List<Object> list = Lists.newArrayList();
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
list.add(sr);
});
System.gc(); // 主動通知垃圾回收
list.forEach(l -> {
Object obj = ((SoftReference<?>) l).get();
System.out.println("Object: " + obj);
});
}
然後我們執行代碼之後:
對於列印結果中,只有最後一個對象保留了下來,其他的obj全都被置空回收了。即說明瞭在記憶體不足的情況下,軟引用將會被自動回收。
對於弱引用,就像我們醫葯箱里的備用藥,當我們需要藥品時,我們會先看看醫葯箱里是否有備用藥。如果醫葯箱里有足夠的藥品(記憶體足夠),我們就可以使用備用藥;但如果醫葯箱里的備用藥不夠了(記憶體不足),我們可能會去藥店購買。在記憶體不足時,垃圾回收器可能會回收軟引用對象,類似於我們在醫葯箱里的備用藥被用完時去藥店購買。
弱引用
JDK1.2之後,用java.lang.ref.WeakReference
來表示弱引用。弱引用與軟引用類似,但強度更弱。即使記憶體足夠,只要沒有強引用指向一個對象,垃圾回收器就可以隨時回收該對象。弱引用適用於需要臨時引用對象的場景,如臨時緩存或臨時存儲對象。也可以用於解決對象之間的迴圈引用問題,避免記憶體泄漏。
對於上述示例中,我們將數組賦值給弱引用
private static final List<Object> list = Lists.newArrayList();
public static void main(String[] args) {
IntStream.range(0, 10).forEach(i -> {
byte[] buff = new byte[1024 * 1024];
WeakReference<byte[]> sr = new WeakReference<>(buff);
list.add(sr);
});
System.gc(); // 主動通知垃圾回收
list.forEach(l -> {
Object obj = ((WeakReference<?>) l).get();
System.out.println("Object: " + obj);
});
}
執行結果發現所有的對象都是null,即都被回收了。
對於弱引用,就像我們正在旅行,使用一張一次性地圖。我們只在需要導航時使用地圖,一旦旅行結束,我們就不再需要地圖了。這時我們可以選擇扔掉地圖,類似於弱引用,在垃圾回收器運行時,無論記憶體是否充足,對象都可能被回收。
虛引用
在 JDK1.2之後,用java.lang.ref.PhantomReference
類來表示虛引用。虛引用是最弱的引用類型,它幾乎對對象沒有任何影響,不能通過虛引用獲取對象,也不能通過它來阻止對象被垃圾回收。從源碼中可以看出它只有一個構造函數和一個 get() 方法,而且它的 get() 方法僅僅是返回一個null,也就是說將永遠無法通過虛引用來獲取對象,虛引用必須要和 ReferenceQueue 引用隊列一起使用。
虛引用可以用於在對象被回收時進行後續操作,如對象資源釋放或日誌記錄,常用於跟蹤對象被垃圾回收的狀態,執行一些清理工作。
而對於弱引用,就像我們去商店,商店入口處的門閂並不直接影響你進入房屋,但它會在有人進入或離開時發出聲音,提醒你有人進店(歡迎光臨)或者離開(歡迎再來)。類似地,虛引用並不直接影響對象的生命周期,但它可以在對象被回收時發出通知,讓你有機會進行一些後續操作,比如資源釋放或者記錄日誌。
引用隊列
引用隊列(ReferenceQueue
)是Java中的一個特殊隊列,用於配合軟引用、弱引用和虛引用,實現更靈活的對象引用和回收管理。
引用隊列的主要作用是跟蹤對象的垃圾回收過程。當一個軟引用、弱引用或虛引用指向的對象被垃圾回收器回收時,如果它們與一個引用隊列關聯,那麼這些引用就會被自動加入到引用隊列中。通過監視引用隊列中的對象,我們可以瞭解到對象的回收狀態,從而執行一些額外的操作,比如資源釋放或日誌記錄等。
總結
Java中的四種引用類型各具特點,可根據程式需求選擇合適的引用類型。強引用保證對象不被意外回收,軟引用和弱引用用於實現緩存或解決記憶體敏感問題,而虛引用則用於對象回收後的通知和清理操作。合理使用引用類型可以更好地管理記憶體和避免記憶體泄漏問題。
本文已收錄於我的個人博客:碼農Academy的博客,專註分享Java技術乾貨,包括Java基礎、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中間件、架構設計、面試題、程式員攻略等