垃圾收集GC(Garbage Collection)是Java語言的核心技術之一, 在Java中,程序員不需要去關心內存動態分配和垃圾回收的問題,這一切都交給了JVM來處理。針對GC我們這篇文章提出以下幾個問題,GC中判定為垃圾的標准,標記垃圾的算法以及回收垃圾的算法。
什么樣的對象才是垃圾?
這個問題其實很簡單,對於Java對象來講,如果說這個對象沒有被其他對象所引用該對象就是無用的,此對象就被稱為垃圾,其占用的內存也就要被銷毀。那么自然而然的就引出了我們的第二個問題,判斷對象為垃圾的算法都有哪些?
標記垃圾的算法
Java中標記垃圾的算法主要有兩種, 引用計數法和可達性分析算法。我們首先來介紹引用計數法。
引用計數法
引用計數法就是給對象中添加一個引用計數器,每當有一個地方引用它,計數器就加 1;當引用失效,計數器就減 1;任何時候計數器為 0 的對象就是不可能再被使用的,可以當做垃圾收集。這種方法實現起來很簡單而且優缺點都很明顯。
-
優點 執行效率高,程序執行受影響較小
-
缺點 無法檢測出循環引用的情況,導致內存泄露
優點我們很好理解,那么什么是循環引用呢?我們舉一個簡單的例子。
public class MyObject { public MyObject childNode; }
public class ReferenceCounterProblem { public static void main(String[] args) { MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); object1.childNode = object2; object2.childNode = object1; } }
從上述代碼中我們可以看出,object1和object2並沒有任何價值,但是他們循環引用,造成內存泄露。
可達性分析算法
這個算法的基本思想就是通過一系列的稱為 “GC Roots” 的對象作為起點,從這些節點開始向下搜索,節點所走過的路徑稱為引用鏈,當一個對象到 GC Roots 沒有任何引用鏈相連的話,則證明此對象是不可用的。
那么什么對象可以作為GCRoot?
-
虛擬機棧中的引用對象
-
方法區中的常量引用對象
-
方法區中的類靜態屬性引用對象
-
本地方法棧中的引用對象
-
活躍線程中的引用對象
可達性分析算法如下圖所示
那么不可達的對象是否是必死之局呢?答案也是否定的
在可達性分析法中不可達的對象,它們暫時處於“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視為沒有必要執行。被判定為需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。
如何將垃圾回收?
在Java中存在着四種垃圾回收算法,標記清除算法、復制算法、標記整理算法以及分代回收算法。我們接下來會分別介紹他們。
標記清除算法
該算法分為“標記”和“清除”兩個階段:標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。它是最基礎的收集算法,效率也很高,但是會帶來兩個明顯的問題:
-
效率問題
-
空間問題(標記清除后會產生大量不連續的碎片)
該算法具體流程如下圖所示
復制算法
為了解決效率問題,我們開發出了復制算法。它可以將內存分為大小相同的兩塊,每次使用其中的一塊。當第一塊的內存使用完后,就將還存活的對象復制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內存回收都是對內存區間的一半進行回收。
簡單來說就是該對象分為對象面以及空閑面,對象在對象面上創建,對象面上存活的對象會被復制到空閑面,接下來就可以清除對象面的內存。
這種算法的優缺點也比較明顯
-
優點:解決碎片化問題,順序分配內存簡單高效
-
缺點:只適用於存活率低的場景,如果極端情況下如果對象面上的對象全部存活,就要浪費一半的存儲空間。
標記整理算法
為了解決復制算法的缺陷,充分利用內存空間,提出了標記整理算法。該算法標記階段和標記清除一樣,但是在完成標記之后,它不是直接清理可回收對象,而是將存活對象都向一端移動,然后清理掉端邊界以外的內存。 如下圖所示:
分代收集算法
當前虛擬機的垃圾收集都采用分代收集算法,這種算法就是根據具體的情況選擇具體的垃圾回收算法。一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。
比如在新生代中,每次收集都會有大量對象死去,所以可以選擇復制算法,只需要付出少量對象的復制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”算法進行垃圾收集。
本篇文章我們介紹了 JavaGC 方面的內容,主要講了判斷垃圾的機制,以及常用的一些垃圾回收算法,希望能夠對你有所幫助。