每天學習一點JVM之:垃圾回收機制

LemonYang發表於2016-12-04

關於JVM系列的文章,都是在讀了《深入理解java虛擬機器》一書之後的讀書筆記總結。

每個人入門java的時候,基本上都會聽到的關於java的一個郵電就是java的記憶體管理功能。使用java的時候不需要將過多的心思擺在記憶體管理的問題上(實際上,記憶體管理是開發者始終關注的話題,尤其是移動app的開發,萬一心情不好就oom了呢)。今天簡單總結一下java的垃圾回收的知識點。

JAVA的引用

java中引用包括下面四種:

  • 強引用

    程式中普遍存在的類似“Object object=new Object()”這種型別的引用屬於強引用。垃圾回收器永遠不會回收被強引用所引用的物件。

  • 軟引用

    用以描述有用但卻並非必需的物件。對於軟引用所引用的物件,在系統將要發生oom異常之前,將會對這些物件列進回收範圍之中進行第二次回收。

  • 弱引用

    用於描述非必需的物件,強度較軟引用弱。軟引用關聯的物件只能生存到下一次垃圾收集發生之前。無論當前記憶體是否足夠,它都會在垃圾收集器開始工作的時候被回收。

  • 虛引用

    也稱為幽靈引用或者幻引用。虛引用的存在不會對物件的生存時間產生影響,也無法通過虛引用取得物件的例項。為物件設定虛引用的唯一目的就是能在這個物件唄垃圾收集器回收時收到一個系統通知。

物件存活的判定

  • 引用計數演算法

    給物件新增一個引用計數器,當有地方引用該物件時,計數器加一;引用失效時,計數器減一。任意時候,只要計數器不為零,就表示該物件尚存在引用關係,否則表示物件不能再被使用。

  • 可達性分析演算法

    以一系列可以被稱為gc-roots的物件為起點並向下搜尋,搜尋走過的路徑為引用鏈,當物件和gc-roots之間沒有任何的引用鏈的話,則該物件是不可用的,如下圖所示:

每天學習一點JVM之:垃圾回收機制

java中可以被看作是gc roots的物件包括:

  • 虛擬機器棧中引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • native方法引用的物件

gc roots不可達的物件,在被回收之前至少要經歷兩次被標記的過程,才能確定是否會被垃圾回收器回收。在物件被發現沒有引用鏈的時候,會被第一次標記並且進行一次篩選(篩選的條件是該物件有沒有必要執行finalize()方法,當物件沒有覆蓋finalize()方法或者該方法已經被虛擬機器呼叫過的話,虛擬機器都會認為沒有必要執行finalize()方法)。如果物件沒能在finalize()方法中重新於引用鏈上的任意物件建立關聯關係,那麼物件將會在第二次標記的時候被回收。

垃圾收集演算法

  • 標記-清除演算法

演算法分為標記和清除兩個階段。首先標記出所有需要回收的物件,在標記完成後統一回收被標記的物件。(這個方法效率較低,而且回收之後會產生大量的不連續的記憶體碎片)示意圖如下所示:

每天學習一點JVM之:垃圾回收機制

  • 複製演算法

將記憶體按容量劃分為等大的兩塊,每次只使用其中的一塊記憶體,當這一塊記憶體將用完的時候就將還存活著的物件複製到另一塊記憶體上面,然後再把已使用過的記憶體空間一次清理掉。示意圖如下所示:

每天學習一點JVM之:垃圾回收機制

這個演算法的一種改進做法是在在回收新生代的時候,將記憶體分為一塊較大的eden空間和兩塊較小的survivor空間,每次使用eden和其中的一塊survivor空間。當回收的時候,將其中還存活的物件一次性地複製到另外一塊survivor空間上,最後清理掉eden和之前使用的survivor空間。

  • 標記-整理演算法

標記過程於標記-清除演算法一樣,但是並不直接對可回收物件進行清理,而讓所有存活的物件都往一端移動,然後清理端邊界以外的記憶體(針對老年代存活率比較高的現象而提出).示意圖以下所示:

每天學習一點JVM之:垃圾回收機制

  • 分代收集演算法

它根據物件的存活週期的不同將記憶體劃分為幾塊,一般是把Java堆分為新生代和老年代。在新生代中,每次垃圾收集時都會發現有大量物件死去,只有少量存活,因此可選用複製演算法來完成收集,而老年代中因為物件存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除演算法或標記—整理演算法來進行回收。

簡單整理如上,如果需要更加深入的學習的話,建議大家可以看一下《深入理解JVM》這本書,個人覺得寫得確實很棒。接下來,還會繼續寫一些這本書的讀書筆記。如果大家喜歡,可以點贊收藏。有什麼問題歡迎評論一起探討。大家也可以關注我,我會在工作之餘分享自己學習android的一些東西。最後,感謝你寶貴的時間閱讀這篇文章,謝謝!

相關文章