《對象搜索算法與回收算法》介紹了垃圾回收的基礎算法,至關於垃圾回收的方法論。接下來就詳細看看垃圾回收的具體實現。算法
上文提到過現代的商用虛擬機的都是採用分代收集的,不一樣的區域用不一樣的收集器。經常使用的7種收集器,其適用的範圍如圖所示微信
Serial、ParNew、Parallel Scavenge用於新生代;
CMS、Serial Old、Paralled Old用於老年代。
而且他們相互之間以相對固定的組合使用(具體組合關係如上圖)。G1是一個獨立的收集器不依賴其餘6種收集器。ZGC是目前JDK 11的實驗收集器。多線程
下面來看看各個收集器的特性併發
Serial,是單線程執行垃圾回收的。當須要執行垃圾回收時,程序會暫停一切手上的工做,而後單線程執行垃圾回收。app
由於新生代的特色是對象存活率低,因此收集算法用的是複製算法,把新生代存活對象複製到老年代,複製的內容很少,性能較好。
單線程地好處就是減小上下文切換,減小系統資源的開銷。但這種方式的缺點也很明顯,在GC的過程當中,會暫停程序的執行。若GC不是頻繁發生,這或許是一個不錯的選擇,不然將會影響程序的執行性能。
對於新生代來講,區域比較小,停頓時間短,因此比較使用。jvm
ParNew一樣用於新生代,是Serial的多線程版本,而且在參數、算法(一樣是複製算法)上也徹底和Serial相同。高併發
Par是Parallel的縮寫,但它的並行僅僅指的是收集多線程並行,並非收集和原程序能夠並行進行。ParNew也是須要暫停程序一切的工做,而後多線程執行垃圾回收。
由於是多線程執行,因此在多CPU下,ParNew效果一般會比Serial好。但若是是單CPU則會由於線程的切換,性能反而更差。性能
新生代的收集器,一樣用的是複製算法,也是並行多線程收集。與ParNew最大的不一樣,它關注的是垃圾回收的吞吐量。線程
這裏的吞吐量指的是 總時間與垃圾回收時間的比例。這個比例越高,證實垃圾回收佔整個程序運行的比例越小。指針
Parallel Scavenge收集器提供兩個參數控制垃圾回收的執行:
由於Parallel Scavenge收集器關注的是吞吐量,因此當設置好以上參數的時候,同時不想設置各個區域大小(新生代,老年代等)。能夠開啓-XX:UseAdaptiveSizePolicy參數,讓JVM監控收集的性能,動態調整這些區域大小參數。
老年代的收集器,與Serial同樣是單線程,不一樣的是算法用的是標記-整理(Mark-Compact)。
由於老年代裏面對象的存活率高,若是依舊是用複製算法,須要複製的內容較多,性能較差。而且在極端狀況下,當存活爲100%時,沒有辦法用複製算法。因此須要用Mark-Compact,以有效地避免這些問題。
老年代的收集器,是Parallel Scavenge老年代的版本。其中的算法替換成Mark-Compact。
CMS,Concurrent Mark Sweep,一樣是老年代的收集器。它關注的是垃圾回收最短的停頓時間(低停頓),在老年代並不頻繁GC的場景下,是比較適用的。
命名中用的是concurrent,而不是parallel,說明這個收集器是有與工做執行併發的能力的。MS則說明算法用的是Mark Sweep算法。
來看看具體地工做原理。CMS整個過程比以前的收集器要複雜,整個過程分爲四步:
因爲最耗費時間的併發標記與併發清除階段都不須要暫停工做,因此總體的回收是低停頓的。
因爲CMS以上特性,缺點也是比較明顯的,
有人會以爲既然Mark Sweep會形成內存碎片,那麼爲何不把算法換成Mark Compact呢?
答案其實很簡答,由於當併發清除的時候,用Compact整理內存的話,原來的用戶線程使用的內存還怎麼用呢?要保證用戶線程能繼續執行,前提的它運行的資源不受影響嘛。Mark Compact更適合「Stop the World」這種場景下使用。
G1,Garbage First,在JDK 1.7版本正式啓用,是當時最前沿的垃圾收集器。G1能夠說是CMS的終極改進版,解決了CMS內存碎片、更多的內存空間登問題。雖然流程與CMS比較類似,但底層的原理已經是徹底不一樣。
高效益優先。G1會預測垃圾回收的停頓時間,原理是計算老年代對象的效益率,優先回收最大效益的對象。
堆內存結構的不一樣。之前的收集器分代是劃分新生代、老年代、持久代等。
G1則是把內存分爲多個大小相同的區域Region,每一個Region擁有各自的分代屬性,但這些分代不須要連續。
這樣的分區能夠有效避免內存碎片化問題。
可是這樣一樣會引伸一個新的問題,就是分代的內存不連續,致使在GC搜索垃圾對象的時候須要全盤掃描找出引用內存所在。
爲了解決這個問題,G1對於每一個Region都維護一個Remembered Set,用於記錄對象引用的狀況。當GC發生的時候根據Remembered Set的引用狀況去搜索。
兩種GC模式:
總體的執行流程:
在Region層面上,總體的算法偏向於Mark-Compact。由於是Compact,會影響用戶線程執行,因此回收階段須要STW執行。
在JDK 11當中,加入了實驗性質的ZGC。它的回收耗時平均不到2毫秒。它是一款低停頓高併發的收集器。
ZGC幾乎在全部地方併發執行的,除了初始標記的是STW的。因此停頓時間幾乎就耗費在初始標記上,這部分的實際是很是少的。那麼其餘階段是怎麼作到能夠併發執行的呢?
ZGC主要新增了兩項技術,一個是着色指針Colored Pointer,另外一個是讀屏障Load Barrier。
着色指針Colored Pointer
ZGC利用指針的64位中的幾位表示Finalizable、Remapped、Marked一、Marked0(ZGC僅支持64位平臺),以標記該指向內存的存儲狀態。至關於在對象的指針上標註了對象的信息。注意,這裏的指針至關於Java術語當中的引用。
在這個被指向的內存發生變化的時候(內存在Compact被移動時),顏色就會發生變化。
在G1的時候就說到過,Compact階段是須要STW,不然會影響用戶線程執行。那麼怎麼解決這個問題呢?
讀屏障Load Barrier
因爲着色指針的存在,在程序運行時訪問對象的時候,能夠輕易知道對象在內存的存儲狀態(經過指針訪問對象),若請求讀的內存在被着色了。那麼則會觸發讀屏障。讀屏障會更新指針再返回結果,此過程有必定的耗費,從而達到與用戶線程併發的效果。
把這兩項技術聯合下理解,引用R大(RednaxelaFX)的話
與標記對象的傳統算法相比,ZGC在指針上作標記,在訪問指針時加入Load Barrier(讀屏障),好比當對象正被GC移動,指針上的顏色就會不對,這個屏障就會先把指針更新爲有效地址再返回,也就是,永遠只有單個對象讀取時有機率被減速,而不存在爲了保持應用與GC一致而粗暴總體的Stop The World。
ZGC雖然目前還在JDK 11還在實驗階段,但因爲算法與思想是一個很是大的提高,相信在將來不久會成爲主流的GC收集器使用。
更多技術文章、精彩乾貨,請關注
博客:zackku.com
微信公衆號:Zack說碼