1. 程式人生 > >JVM—垃圾回收GC演算法

JVM—垃圾回收GC演算法

# 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