Java虛擬機器之垃圾回收演算法

weixin_34236497發表於2018-10-15

1. 垃圾回收器如何確定物件死亡

1.1 引用計數法

實現原理:給物件中新增一個引用計數器,每當有引用它時,計數器就加1,如果引用失效,計數器值就減1,計數器為0的物件就是沒有被再使用過的物件。

優點:引用計數演算法實現起來較為簡單,判定效率也很高。
缺點:無法解決互相迴圈引用的問題。

1.2 可達性分析演算法

實現原理:通過一系列的稱為GC Roots的物件作為起始點,當一個物件到GC Roots沒有任何引用鏈(Reference Chain)相連時,則表明此物件不可用

2006464-04f541b50ac19eac.png

注意:在Java語言中,可作為GC Roots的物件包括下面幾種:

虛擬機器棧(虛擬機器棧中的本地變數表)中引用的物件。
方法區中靜態類屬性引用的物件
方法區中常量引用的物件
本地方法棧中native方法引用的物件

注意:在可達性分析演算法中,並不是不可達的物件一定會被回收,至少要經歷兩次標記過程:

如果某個物件在進行可達性分析演算法之後沒有發現和GC Roots的引用鏈,它會被標記一次並且進行篩選,篩選的條件就是該物件是否有必要執行finalize()方法,當物件沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機器呼叫過,虛擬機器在這兩種情況下都會認為沒必要執行

如果一個物件被判定為有必要執行finalize()方法,那麼這個物件會被放置在F-Queue佇列中,在稍後會由虛擬機器建立一個,低優先順序的Finalizer執行緒去執行它,但不會等它結束

最後一次掙扎

finalize()方法是物件逃脫死亡的最後一次機會,稍後GC將會對F-Queue中的物件進行第二次小規模的標記,如果物件要在finalize()方法中進行自救,只需要重新與引用鏈上任何一個物件建立關聯即可。

2. 垃圾回收演算法

2.1 標記-清除演算法(Mark-Sweep)

顧名思義,分為標記清除兩個階段:標記出所有需要回收的物件,然後統一回收所有被標記的物件。

2006464-6e8966f819653ca1.png

缺點:標記和清除的效率都不高,標記清除後會有大量不連續的記憶體碎片,而過多的記憶體碎片,會導致後續分配大物件時無法找到足夠的連續記憶體空間,繼而觸發另一次垃圾回收動作

2.2 複製演算法(Copying)

將可用記憶體分為大小相同兩塊。每次只用一塊,當一塊空間用完了,就將還存活的物件複製到另一塊上,然後將剛使用過的記憶體空間一次清理掉。這樣使得每次都是對其中的一塊進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況。實現簡單,執行高效。

2006464-057fb30fb0ae5ca7.png

優點:演算法實現簡單,記憶體效率高,不易產生碎片。
缺點:可用記憶體被壓縮到了原本的一半。且存活物件增多的話,Copying演算法的效率會大大降低。

2.3 標記-整理(Mark-Compact)

複製收集演算法在物件存活率高的時候就要執行較多的複製操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保用於應付半區記憶體中所有物件都100%存活的極端情況,所以在老年代一般不能直接選用這種演算法。
原文地址
https://blog.csdn.net/byhook/article/details/83046041
因此人們提出另外一種“標記-整理”(Mark-Compact)演算法由於老年代中的物件生存週期都較長,有人提出“標記-整理”演算法,標記過程和“標記-清理”一樣,但在清除已死物件的同時會對存活物件進行整理,這樣可以減少碎片空間。

2006464-1591c0db12e2aa8c.png

2.4 分代收集演算法

當前商業虛擬機器的垃圾收集都是採用分代收集(Generational Collecting)演算法,這種演算法並沒有什麼新的思想出現,只是根據物件不同的存活週期將記憶體劃分為幾塊。一般是把Java堆分作``新生代和老年代,這樣就可以根據各個年代的特點採用最適當的收集演算法。在新生代中,每次垃圾收集時都發現有大批物件死去,只有少量存活,那就用複製演算法,只要少量複製成本就可以完成收集。而老年代中因為物件的存活率較高、週期長,就用標記-整理標記-清除演算法來回收。

新生代複製回收機制

將新生代區域分為EdenFrom SurvivorTo Survivor,比例為8:1:1

2006464-bc5bcee1d7d61f6e.png

實現原理
a. Eden區域最大,對外提供堆記憶體,當Eden區快滿的時候,進行Minor GC,把存活物件放入From Survivor區域,然後清空Eden區域,移動之後會標記相應的age為1,這樣就 完成了一次Minor GC
b. 由於Eden區域被清空,對外提供堆記憶體,當Eden區域又快滿的時候,就會再次觸發Minor GC,將Eden區域和From Survivor區域中存活的物件複製到To Survivor區域中,然後將Eden以及From Survivor區域清空,標記從Eden複製到To Survivor區域中的物件年齡設定為1,再將從From Survivor區域中複製過去的物件年齡+1,這樣完成了第二次GC
c. 同上,當Eden區域再次快滿的時候,就會將Eden區域以及To Survivor區域中的存活物件,複製到From Survivor區域中,標記從Eden複製到From Survivor區域中的物件年齡設定為1,再將從To Survivor區域中複製過去的物件年齡+1,這樣完成了第三次GC
d. ......後面的步驟跟之前的b、c步驟交替進行
e. 當一個存活物件的年齡熬過一定次數之後(預設為15次),該物件就會被移動到老年代。

老年代-標記整理回收機制

老年代一般存放的是存活時間較久的物件,所以每一次 GC 時,存活物件比較較大,也就是說每次只有少部分物件被回收。
因此,根據不同回收機制的特點,這裡選擇 存活物件多,垃圾少 的標記整理 回收機制,僅僅通過少量地移動物件就能清理垃圾,而且不存在記憶體碎片化。

3 垃圾收集器

如果說垃圾收集演算法是記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。

3.1 Serial收集器

缺點:它在進行垃圾回收的時候,必須暫停所有的工作執行緒。

3.2 ParNew收集器

它是Serial收集器的多執行緒版本,用於新生代記憶體回收,可以配合CMS使用

3.3 Parallel Scavenge收集器

它是一個新生代收集器,也是使用複製演算法的收集器,它的目標是達到一個可控制的吞吐量。

3.4 Serial Old收集器

他是Serial收集器的老年代版本,使用的是標記整理演算法。

3.5 Parallel Old收集器

他是Parallel Scavenge收集器老年代版本,使用的是標記整理和多執行緒。

3.6 CMS收集器

它是一種以獲取最短停頓時間為目標的收集器。基於標記-清除演算法實現的,整個過程如下:
初始標記(CMS initial mark)
併發標記(CMS concurent mark)
重新標記(CMS remark)
併發清除(CMS initial sweep)
其中初始標記重新標記依然需要暫停工作執行緒。

優點:併發手機,停頓低。

原文地址
https://blog.csdn.net/byhook/article/details/83046041
參考:
《深入理解Java虛擬機器》

相關文章