Java強引用、軟引用、弱引用及虛引用深入探討


強引用、軟引用、弱引用和虛引用深入探討

為了更靈活的控制對象的生命周期,在JDK1.2之后,引用被划分為強引用、軟引用、弱引用、虛引用四種類型,每種類型有不同的生命周期,它們不同的地方就在於垃圾回收器對待它們會使用不同的處理方式。

引用類型在日常開發中並不常關注,也很少注意到,因此很多人忽略了它們的存在,而事實上,引用類型在Java體系中扮演着十分重要的角色,要想對Java體系有一個更深層次的理解,了解和掌握這些引用的用法是十分必要的。

在正式開始前,我們先來上兩道開胃菜。

為什么需要回收

每一個Java程序中的對象都會占用一定的計算機資源,最常見的,如:每個對象都會在堆空間上申請一定的內存空間。但是除了內存之外,對象還會占用其它資源,如文件句柄,端口,socket等等。當你創建一個對象的時候,必須保證它在銷毀的時候會釋放它占用的資源。否則程序將會在OOM中結束它的使命。

在Java中不需要程序員來管理內存的分配和釋放,Java有自動進行內存管理的神器——垃圾回收器,垃圾回收器會自動回收那些不再使用的對象。

在Java中,不必像C或者C++那樣顯式去釋放內存,不需要了解其中回收的細節,也不需要擔心會將同一個對象釋放兩次而導致內存損壞。所有這些,垃圾回收器都自動幫你處理好了。你只需要保證那些不再被使用的對象的所有引用都已經被釋放掉了,否則,你的程序就會像在C++中那樣結束在內存泄漏中。

雖然垃圾回收器確實讓Java中的內存管理比C、C++中的內存管理容易許多,但是你不能對於內存完全不關心。如果你不清楚JVM到底會在什么條件下才會對對象進行回收,那么就有可能會不小心在代碼中留下內存泄漏的bug。

因此,關注對象的回收時機,理解JVM中垃圾收集的機制,可以提高對於這個問題的敏感度,也能在發生內存泄漏問題時更快的定位問題所在。想了解更多關於垃圾回收相關的細節,可以參考這篇文章

為什么需要引用類型

引用類型是與JVM密切合作的類型,有些引用類型甚至允許在其引用對象在程序中仍需要的時候被JVM釋放。

那么,為什么需要這些引用類型呢?

在Java中,垃圾回收器線程一直在默默的努力工作着,但你卻無法在代碼中對其進行控制。無法要求垃圾回收器在精確的時間點對某些對象進行回收。

有了這些引用類型之后,可以一定程度上增加對垃圾回收的粒度把控,可以讓垃圾回收器在更合適的時機回收掉那些可以被回收掉的對象,而並不僅僅是只回收不再使用的對象。

這些引用類型各有特點,各有各的適用場景,清楚的了解和掌握它們的用法可以幫助你寫出更加健壯的代碼。

說明

在JDK 1.2以前的版本中,如果一個對象沒有被任何變量引用,那么程序就無法再使用這個對象。也就是說,只有當對象處於可達(reachable)狀態時,程序才能使用它。只有在對象沒有任何其他對象引用它時,垃圾回收器才會對它進行收集。對象只有被引用和沒有被引用兩種狀態。這種方式無法描述一些“食之無味,棄之可惜”的對象。

而很多時候,我們希望存在這樣一些對象:當內存空間足夠時,可以將它們保存在內存中,不進行回收;當內存空間變得緊張時,允許JVM回收這些對象。大部分緩存都符合這樣的場景。

從JDK 1.2版本開始,Java對引用的概念進行了擴充,對象的引用分成了4種級別,從而使程序開發者能更加靈活地控制對象的生命周期,更好的控制創建的對象何時被釋放和回收。

這4種級別由高到低依次為:強引用軟引用弱引用虛引用

實力翻車

歡迎來到大型翻車現場,接下來將實力演示一波因為強引用過多導致的翻車例子。

如果你需要在整個程序運行期間保存一些對象(因為它們的初始化很耗費時間和資源),你可能會使用靜態集合對象來存儲並且在代碼中隨處使用它們。

public static Map<K, V> storedObjs = new HashMap<>();

但是這樣,就會阻止垃圾回收器對集合中的對象進行回收和銷毀。從而可能導致OOM的發生。例如:

public class OOMTest {
    public static List<Integer> cachedObjs = new ArrayList<>();
 
    public static void main(String[] args) {
        for (int i = 0; i < 100_000_000; i++) {
            cachedObjs.add(i);
        }
    }
}

輸出如下:

Exception in thread “main” java.lang.OutOfMemoryError: Java heap space

這樣就符合預期的翻車了。但你也許會說,誰會這么無聊,創建這么多變量。

嗯,確實是的,但是別忘了,一個程序可能會運行很長時間,幾個月,甚至幾年(如果你的代碼和公司足夠健壯的話),如果期間不斷的創建變量而不清理的話(像上面那樣把HashMap當緩存使用),是有可能會導致這種情況發生的。

內容編排

接下來的文章將從以下幾方面對這四種引用進行介紹:

注意 本系列文章都是以JDK1.8 版本的代碼進行分析,不同版本中代碼會略有差異。

如果只是想要對這些引用進行簡單了解,那么看完簡要介紹部分即可,如果想要有更深入的研究,可以繼續查閱源碼剖析部分。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM