JVM垃圾回收演算法的優缺點
最近在學習JVM的一些知識,所以特意寫下學習筆記來簡單記錄知識點,由於只是初步的學習,下面本人所總結的內容都比較簡單且不一定正確,如果有什麼錯誤希望大家能指出來,我看到後會進行修正。
垃圾分析演算法
功能:分析JVM堆上哪些物件是“垃圾”
引用計數法
每一個物件都有一個引用計數器,當被引用一次時,它都會 +1,引用取消時 -1,當執行GC時,所有引用計數器為 0 的物件都會被視為“垃圾”。
優點:相對於其他垃圾回收演算法,標記清除法實現相對簡單。
缺點:當兩個物件相互引用時,就會無法被清除;。
可達性分析演算法(主流)
以方法區的靜態變數或棧針變量表的變數為Root根節點,通過這個root去找其他下級節點,無法到達的物件在GC中會被清理。
垃圾收集演算法
標記清除演算法
把所有需要回收的物件作一個標記,GC回收的時候會把所有被標記的物件回收。
優點:簡單
缺點:垃圾回收後記憶體會變得不連續,造成很大零散的記憶體區域。以後新建立的大物件可能會不夠空間儲存。
複製演算法(大多JVM的GC都使用這個)
通俗點說,把原本存放物件的一塊大區域拆分為 1個Eden和2個Survivor,建立新物件時僅使用其中一塊區域Eden,當Eden滿了後觸發minorGC,清空Eden和其中一個Survivor1前把倖存的物件複製到Survivor2,然後再清空Eden和Survivor1(僅概念上簡述,實際情況很複雜,請看其他文章詳細分析)
優點:GC後的記憶體空間是連續的。
缺點:由於分出了Survivor2不存放物件,真正存放新物件的記憶體區域會變少,Eden:Survivor1:Survivor2比例為8:1:1,少了10%的可用記憶體。
標記-整理法
類似與標記清除法一樣,第一步先把需要回收的物件標記,不同的是第二步把活動的物件(倖存)往記憶體一邊移動。堆記憶體就像一列隊伍,把所有要留下的物件都往前靠,後面剩下的都是即將回收的物件(垃圾)。
優點:避免了“標記清除法”和“複製演算法”的缺點。
缺點:效率比較低。
分代收集法
書中把這個演算法和垃圾收集演算法放在了一起,但我覺得分代收集與上面的“垃圾收集演算法”有一點本質區別。前者是GC的設計方式,後者是GC回收的具體思路。
堆記憶體被分為 新生代和老年代,其中 新生代一般分為eden和Survivor(上文簡述過)。在新生代的gc成為 minorGC,老年代的稱為majorGC/fullGC,新建立的物件屬於新生代,在經歷了N次minorGC後(N可調大小,預設15),會轉到老年代。
新生代的區域和老年代區域比例一般為1:2。兩個區域的垃圾收集演算法都可以不同,新生代一般為“複製演算法”,老年代為“標記-整理法”。
為什麼新生代使用“複製演算法”,老年代使用“標記-整理法”?
因為在Java程式中,大部分物件都是“朝生夕死”,很快會被回收的,所以新生代的minorGC的觸發頻率要遠大於老年代的majorGC,這就需要效率更高的“複製演算法”,而老年代由於majorGC觸發頻率比較低,所以可以選擇效率較低的“標記整理法”來節約記憶體。
此外,值得一提的是新生代的eden記憶體要大於Survivor記憶體,這樣就會出現一個問題:當eden中有一部分物件生命週期一樣並且佔用的記憶體大於Survivors時,執行minorGC仍然倖存,Survivor將無法存放這麼多的物件。這時老年代就會起到“擔保”作用,那些存活的物件將直接晉升到老年代。同理“複製演算法”需要有人來做擔保,這也是老年代使用“標記整理法”的原因之一。