Java虛擬機垃圾回收算法

熟悉 Java 的朋友一定知道 Java 虛擬機了,熟練掌握 Java 虛擬機是一個高級工程師的基礎素養哦,當然面試官在問到 Java 虛擬機的時候,一定會問到 垃圾回收算法的。

2020年的金三銀四很快就要來了,不知道朋友們有沒有在夜深人靜的時候,放下手機裏的小姐姐,仔細捋一捋垃圾回收算法呢?沒有也沒關係哦,今天我們就來一起捋一下。

 

技術背景需要先了解一下

按道理,裝逼的正確姿勢需要從什麼是 Java,Java 的發展史,Java 虛擬機說起的,但我們今天不裝逼,假設各位聰明的小夥伴都知道了,我們直奔主題

 

哪些內存需要回收

大家都知道,虛擬機的內存結構包括以下五個區域:方法區、堆、虛擬機棧、本地方法棧、程序計數器。其中,虛擬機棧、本地方法棧、程序計數器是線程私有的區域,隨線程生而生,隨線程滅而滅,所以不存在垃圾回收的說法。我們重點需要關注的是堆和方法區兩個區域。

在回收之前,需要知道哪些對象可以回收,哪些對象不可以被回收。這就需要知道哪些對象不可回收的算法。

1、引用計數算法

引用計數算法是垃圾回收的早期策略,也是一個非常簡單和高效的策略。主要原理是:給對象添加一個引用計數器,每當有一個地方引用到這個對象時,就給對應的計數器加1;當引用失效,超過了生命週期或者設置爲一個新值時,就給計數器減1. 任何計數器爲0的對象,就是垃圾收集的對象了。

但是有一個缺點是,它很難解決對象之間的循環引用問題。

比如,對象1的一個屬性引用了對象2,對象2的一個屬性引用了對象1,當把對象1和對象2都設置爲 null 時,堆中它們的屬性仍然引用着對方,導致永遠都不可能被回收了。

 

2、可達性分析算法

主流的商用編程語言,如 Java,C#,古老的 Lisp,的實現都是用可達性分析算法來判定對象是否存活的。它的理論基礎是離散數學的圖論,即把所有的引用關係看成是一張圖,通過一系列的稱爲 “GC Roots” 的對象作爲起點,從這些節點開始向下搜索,直到遍歷完所有對象,最終那些沒有被遍歷到的對象就可以被判定位可回收的對象。

如上圖,ObjD 和 Obj E,就是可以回收的對象

 

常用的垃圾回收算法

說了半天,終於到主角登場了。

1、標記-清除算法

顧名思義,算法分爲 “標記” 和 “清除” 兩個階段。

標記階段,根據上文提到的垃圾判定的算法來標記哪些對象時垃圾;

清除階段,標記完成後,統一回收所有被標記對象。

 

它是最基礎的算法,因爲後續的算法都是基於這種思路,並且對其不足的地方改進後得到的。

 

它有兩個缺點,一是效率不高,標記和清除兩個階段的效率都不高;二是空間問題,標記清除之後,會產生大量的不連續的內存碎片。

2、複製算法

複製算法的提出是爲了克服效率和碎片過多的問題。它的基本思想是:將內存分爲大小相同的兩塊,每次只使用其中的一塊。當其中一塊的對象滿了,就用標記算法,將還存活的對象移動到另外一塊上,然後一次性回收掉這一塊對象。

因爲每次都是對一整塊內容進行回收,也就不用考慮內存碎片等複雜的情況,只要移動棧頂的指針,按順序分配內存即可。

現代的商業虛擬機都是採用這種算法來回收新生代。但是不是 1:1 分配的,而是將內存分爲一塊較大的 Eden 區和兩塊較小的 Survivor 區。每次只使用 Eden 和 其中一塊 Survivor 區。每次回收時,把 Eden 和 一塊 Survivor 區還存活的對象一次性複製到 另一塊 Survivor 區來,最後清理掉 Eden 和 其中一塊 Survivor 區。

3、標記整理算法

標記 - 整理算法 採用 和 標記 - 清除算法一樣的方式進行對象的標記,但在清除時不同,不是直接清除,而是把所有的對象都向一端移動,然後直接清理掉端邊界以外的內存

4、分代收集算法

當代虛擬機的垃圾都是採用了 “分代收集” 算法,它並沒有新的思想,而是根據對象聲明週期不同,將內存 劃分爲若干個不同的區域,一般講內存劃分爲“新生代”和“老年代”,在堆之外,還有一個“永久代”。

 

在 “新生代” 中,每次都有大量的垃圾需要回收,那就選用複製算法;而在 “老年代” 中,對象的存活率高,就必須使用 “標記 - 清理” 或者 “標記 - 整理” 的算法來進行回收。

 

好了,到這裏爲止,垃圾回收的相關算法思想都講完了,但是還是遠遠不夠的,因爲不同的垃圾收集器,使用了不同的垃圾回收算法,回收的策略也會不一樣,後續還會繼續更新。

敬請持續關注 【程序員凱】 公衆號。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章