標籤:參考 1.0 垃圾 tin 執行 進入 整理 相等 記憶體地區
1. 前言
1.1 概念:清理記憶體中不會再被使用的對象
1.2 背景:如果記憶體中的垃圾不被清理,會導致記憶體溢出
1.3 常用的記憶體回收演算法:引用計數法(Reference Counting)、標記清除法(Mark-Sweep)、複製演算法(Copying)、標記壓縮法(Mark-Compact)、分代演算法(Generational Collecting)及分區演算法(Region)
2. 演算法演化
2.1 引用計數法【Java記憶體回收未採用】
2.1.1 思想:對於對象A,如果被引用,A的引用計數器就+1;當引用失效時,A的引用計數器-1.當對象A的引用計數器的值為0,則對象A被回收。
2.1.2 缺陷:
2.1.2.1 無法處理循環參考的情況。【例如:對象A和對象B相互引用,卻沒有被其他對象引用。此時,A和B應該被回收。然而,對象A和B的引用計數器卻不為0,無法被回收。】
2.1.2.2 每次對象被引用/釋放引用的時候,都會伴隨著運算操作,對系統效能會有一定的影響。
因為循環參考的對象無法被回收,為瞭解決該缺陷,於是產生了可達對象的概念。根據可達對象的概念,產生了標記清除法。
2.2 標記清除法【現代記憶體回收演算法的基礎】
2.2.1 基礎:
2.2.1.1 可達對象:通過根對象進行引用搜尋,最終可以達到的對象。
2.2.1.2 不可達對象:通過根對象進行引用搜尋,最終沒有被引用到的對象。
2.2.2 思想:通過標記、清除兩個階段實現記憶體回收
2.2.2.1 標記階段:根據根節點,標記所有的可達對象
2.2.2.2 清除階段:清除所有的不可達對象
2.2.3 缺陷:
2.2.3.1 產生大量的空間片段,工作效率低
標記清除法是對一塊連續的空間進行回收,回收後的空間是不連續的【】。在對象的堆空間分配過程中,尤其是大對象的記憶體配置,不連續記憶體空間的工作做效率要低於連續空間。
因為不連續記憶體空間的工作效率低,為瞭解決這個問題,於是想到:新開闢一個工作空間,將可達對象複製到新的記憶體空間,保證空間使用的連續性。
2.3 複製演算法【新生代演算法】
2.3.1 思想:將原有的記憶體空間分為兩塊,每次使用其中一塊。記憶體回收時,將正在使用的記憶體中的可達對象複製到未使用的記憶體塊中,清除正在使用的記憶體塊中的所有對象,交換兩個記憶體的角色,完成回收。
2.3.2 缺陷:
2.3.2.1 將系統記憶體折半,佔用的系統記憶體太大。
複製演算法的高效性是建立在存活對象少,垃圾對象對象多的前提下。對於存活對象多的情況,複製成本高,需要其它演算法來替代。
2.4 標記壓縮法【老年代演算法】
2.4.1 原理:標記清除演算法執行完成後,再進行一次記憶體磁碟重組。
2.4.2 思想:從根節點出發,將所有的可達對象進行標記,然後將標記對象壓縮到記憶體的一端。最後,清除邊界之外的所有空間。
在上述演算法中,它們都有各自獨特的優勢,並沒有一種演算法可以完全替代其它演算法。因此,正確的方式是:根據記憶體回收對象的特性,使用合適的演算法回收。基於這種情況,產生了分代的思想。
2.5 分代演算法
2.5.1 思想:根據對象的特點,將記憶體地區分成幾塊;根據每塊記憶體地區的特點,使用不同的回收演算法。
2.5.2 應用:新生代使用複製演算法,老年代使用標記壓縮法。
既然可以根據對象的生命週期(時間角度)進行回收,那麼根據對象的空間分布(空間角度)也可以進行回收。
2.6 分區演算法
2.6.1 背景:在相同條件下,堆空間越大,一次GC所用的時間越長,從而產生的停頓也越長。
2.6.2 思想:將堆空間劃分成連續的小地區,每個小地區獨立使用、獨立回收。
3. 補充
3.1 新生代串列記憶體回收行程中,使用複製演算法的思想。
3.1.1 基礎:新生代分為eden區、from區、to區。其中,from區和to區也被稱為survivor區,可以視為用於複製的大小相等、地位相等、且可以角色互換的兩個空間塊。
3.1.2 思路:記憶體回收時,Eden區和正在使用的survivor區(假設是from區)中的可達對象會被複製到未被使用的survivor區(to區),然後清空Eden區和from區。
3.1.3 備忘:from區中的大對象或者老年對象會直接進入老年區;如果to區空間不足,對象也會進入老年區
3.2 複製演算法的高效性是建立在可達對象少,不可達對象多的前提下,因此複製演算法只用於新生代【新生代,垃圾對象通常會多於存活對象;老年代大部分對象都是存活對象】。老年代採用標記壓縮法
3.3 新生代GC與老年代GC對比:
4. 附錄
4.1 參考文獻:《實戰Java虛擬機器 – JVM故障診斷與效能最佳化》 葛一鳴著
JVM之GC演算法