JVM—垃圾回收GC演算法
阿新 • • 發佈:2020-03-07
# 1 GC演算法簡介
|演算法| 特點 |
|--|--|
|**標記-清除**| 分為“標記”和“清除”兩個階段|
|**複製**|可以解決效率問題,將可用的記憶體按容量劃分為大小相等的兩塊。|
|**標記-整理** | 先標記、再整理,最後清理 |
|**分代收集**|劃分新生代和老年代|
# 2 標記-清除
## 2.1 流程
分為“標記”和“清除”兩個階段:
(1)首先標記出所需要回收的物件(引用計數法和可達性分析,兩次標記過程);
(2)在標記完成後統一回收所有被標記的物件。
## 2.2 缺點
(1)效率問題:標記和清除兩個過程的效率不高;
(2)空間問題:標記清除後會**產生大量不連續的記憶體碎片**,導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。
# 3 複製
## 3.1 流程
可以解決效率問題,將可用的記憶體**按容量劃分為大小相等的兩塊**。
(1)每次只使用其中的一塊;
(2)當這一塊用完了,就將還存活的物件複製到另一塊上;
(3)然後再把已使用的記憶體空間清理掉。
## 3.2 優點
**每次對整個半區**進行記憶體回收,避免記憶體碎片問題,只需**移動堆頂指標**,按順序分配記憶體即可,實現簡單,執行高效。
## 3.3 缺點
將**記憶體縮小為原來的一半**,代價高;當物件存活率較高時需要進行較多的複製操作,效率降低。
## 3.4 應用
回收**新生代**,新生代中分為Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中的一塊Survivor。預設**Eden:Survivor=8:1**,Survivor不夠時,老年代記憶體分配擔保。
# 4 標記-整理
## 4.1 流程
(1)首先標記處所需要回收的物件;
(2)不直接對可回收物件進行清理,讓所有存活的物件都**向一端移動**;
(3)直接清理掉端邊界以外的記憶體。
## 4.2 優點
改進了複製演算法在物件存活率較高時帶來的效率問題;
## 4.3 應用
老年代收集(物件存活率較高)
# 5 分代收集
## 5.1 思想
根據物件存活週期的不同將記憶體劃分為新生代和老年代,根據各自的特點採用合適的收集演算法。
(1)新生代中,每次垃圾收集時都發現有大批物件死去,少量存活,選用複製演算法;
(2)老年代中,物件存活率高、沒有額外空間進行分配擔保,使用“標記-清理”或者“標記-整理”。
# 6 QA
## 6.1 為什麼不是1塊Survivor空間而是2塊?
這裡涉及到一個新生代和老年代的存活週期的問題,比如一個物件在新生代經歷15次GC回收,就可以移到老年代了。問題來了,當我們第一次GC的時候,我們可以把Eden區的存活物件放到Survivor-1空間,但是第二次GC的時候,Survivor-1空間和Eden區的存活物件也需要再次用複製演算法,放到Survivor-2空間上,而把剛剛的Survivor-1空間和Eden空間清除。第三次GC時,又把Survivor-2空間和Eden區的存活物件複製到Survivor-1空間,如此反覆。
所以,這裡就需要2塊Survivor空間來回倒騰。
## 6.2 為什麼Eden空間這麼大而Survivor空間要分的少一點?
1. 新建立的物件都是放在Eden空間,這是很頻繁的,尤其是大量的區域性變數產生的臨時物件,這些物件絕大部分都應該馬上被回收,能存活下來被轉移到survivor空間一般不是很多。所以,設定較大的Eden空間和較小的Survivor空間是合理的,大大提高了記憶體的使用率,緩解了複製演算法的缺點。
2. 8:1:1這種比例就挺好的,當然這個比例是可以調整的,包括上面的新生代和老年代的1:2的比例也是可以調整的。
3. 新的問題又來了,從Eden空間往Survivor空間轉移的時候,如果出現Survivor空間不夠了怎麼辦?直接放到老年代去。 有的物件來回在Survivor-1區或者Survivor-2區呆了比如15次,就被分配到老年代Old區;有的物件太大,超過了Eden區,直接被分配在Old區;有的存活物件,放不下Survivor區,也被分配到Old區。如果老年代Old區也被放滿了,就是一次大GC即為Majo