Java的垃圾回收機制


垃圾收集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?

  • 虛擬機棧中的引用對象

  • 方法區中的常量引用對象

  • 方法區中的類靜態屬性引用對象

  • 本地方法棧中的引用對象

  • 活躍線程中的引用對象

可達性分析算法如下圖所示

0c7506f0dd23f0e6f7d5f9c98c29d95e.jpeg0c7506f0dd23f0e6f7d5f9c98c29d95e.jpeg

那么不可達的對象是否是必死之局呢?答案也是否定的

在可達性分析法中不可達的對象,它們暫時處於“緩刑階段”,要真正宣告一個對象死亡,至少要經歷兩次標記過程;可達性分析法中不可達的對象被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或 finalize 方法已經被虛擬機調用過時,虛擬機將這兩種情況視為沒有必要執行。被判定為需要執行的對象將會被放在一個隊列中進行第二次標記,除非這個對象與引用鏈上的任何一個對象建立關聯,否則就會被真的回收。

如何將垃圾回收?

在Java中存在着四種垃圾回收算法,標記清除算法、復制算法、標記整理算法以及分代回收算法。我們接下來會分別介紹他們。

標記清除算法

該算法分為“標記”和“清除”兩個階段:標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所占用的空間。它是最基礎的收集算法,效率也很高,但是會帶來兩個明顯的問題:

  • 效率問題

  • 空間問題(標記清除后會產生大量不連續的碎片)

該算法具體流程如下圖所示

57b736ac2feb667814051089be175047.jpeg57b736ac2feb667814051089be175047.jpeg

復制算法

為了解決效率問題,我們開發出了復制算法。它可以將內存分為大小相同的兩塊,每次使用其中的一塊。當第一塊的內存使用完后,就將還存活的對象復制到另一塊去,然后再把使用的空間一次清理掉。這樣就使每次的內存回收都是對內存區間的一半進行回收。

簡單來說就是該對象分為對象面以及空閑面,對象在對象面上創建,對象面上存活的對象會被復制到空閑面,接下來就可以清除對象面的內存。

這種算法的優缺點也比較明顯

  • 優點:解決碎片化問題,順序分配內存簡單高效

  • 缺點:只適用於存活率低的場景,如果極端情況下如果對象面上的對象全部存活,就要浪費一半的存儲空間。

標記整理算法

為了解決復制算法的缺陷,充分利用內存空間,提出了標記整理算法。該算法標記階段和標記清除一樣,但是在完成標記之后,它不是直接清理可回收對象,而是將存活對象都向一端移動,然后清理掉端邊界以外的內存。 如下圖所示:

a680d77ff6a27e87a73f5a1b891eecae.jpega680d77ff6a27e87a73f5a1b891eecae.jpeg

分代收集算法

當前虛擬機的垃圾收集都采用分代收集算法,這種算法就是根據具體的情況選擇具體的垃圾回收算法。一般將 java 堆分為新生代和老年代,這樣我們就可以根據各個年代的特點選擇合適的垃圾收集算法。

比如在新生代中,每次收集都會有大量對象死去,所以可以選擇復制算法,只需要付出少量對象的復制成本就可以完成每次垃圾收集。而老年代的對象存活幾率是比較高的,而且沒有額外的空間對它進行分配擔保,所以我們必須選擇“標記-清除”或“標記-整理”算法進行垃圾收集。

本篇文章我們介紹了 JavaGC 方面的內容,主要講了判斷垃圾的機制,以及常用的一些垃圾回收算法,希望能夠對你有所幫助。


免責聲明!

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



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