面試官,不要再問我「Java 垃圾收集器」了
- 2019 年 10 月 25 日
- 筆記
如果Java虛擬機中標記清除演算法、標記整理演算法、複製演算法、分代演算法這些屬於GC收集演算法中的方法論,那麼「GC收集器」則是這些方法論的具體實現。
在面試過程中這個深度的問題涉及的比較少,但對於理解上面的這些演算法有很好的幫助。如果能夠如數家珍,也是面試中的加分項,還是那句話,畢竟面試官的時間也不多了。
概念準備
在學習Java GC收集器之前,需要先了解一些內容和概念,首先如果沒有學習《面試官,不要再問我「Java GC垃圾回收機制」了》的可先學習該篇文章,了解基本演算法方法論。
下面了解幾個概念以幫助後面的學習:執行緒暫停(Stop The World)、安全點(Safepoint)、安全區(Safe region)。
在執行可達性分析的時候會出現在分析的過程中對象關係引用等發生了變化,為了保證分析的準確性,就必須在分析的過程中暫停所有Java執行緒,Sun將這一事件稱作「Stop The World」。
那麼,什麼時候暫停合適呢?並不是所有的時刻都可以暫停所有執行緒進行GC的,只有到達某些點才可以進行GC操作,這些點就稱作安全點(Safepoint)。
安全點的設置不能太少,那樣GC等待的時間就會太長,但也不能太多否則會增加運行時的負擔。
所以,安全點的選定基本上是以程式「是否具有讓程式長時間執行的特徵」為標準進行選定的。比如,循環的末尾、方法臨返回前/調用方法的call指令後、可能拋異常的位置等。
HotSpot採用主動中斷的方式,讓執行執行緒在運行期輪詢是否需要暫停的(GC設置的)標誌,若需要則中斷掛起。
對於正在運行的執行緒,可以主動運行到安全點並暫停執行,但是對於那些正在Sleep或阻塞的執行緒,當它們重新執行時可能已經過了安全點,但此時GC可能還沒完成垃圾回收,這種情況該怎麼辦呢?
於是就有了安全區(Safe region)的概念,安全區是一塊區域,在該區域中引用都不會被修改。比如,執行緒進入到安全區的時候先標識自己進入了安全區,等它被喚醒準備離開時,先檢查GC是否完成,如果完成則可以離開,否則就在安全區等待。
了解了上面的基本概念之後,下面正式進入垃圾收集器的講解。
垃圾收集器分類
先通過下圖了解一下Hotspot的8種垃圾收集器及其應用。
兩個收集器之間的連線,表示它們可以搭配使用。收集器所處的區域表示它是屬於新生代收集器還是老年代收集器。其中ZGC為Java11引入的新的垃圾收集器。
默認垃圾收集器
不同Java版本採用的默認收集器如下。
Serial收集器
Serial收集器是最基本、發展歷史最悠久的收集器,是一個單執行緒的收集器。在進行垃圾收集時,必須暫停其他所有的工作執行緒,直到它收集結束。就是所謂的「Stop The World。」
ParNew收集器
ParNew收集器其實就是Serial收集器的多執行緒版本。除了使用多執行緒進行垃圾收集外,其餘行為包括Serial收集器可用的所有控制參數、收集演算法(複製演算法)、Stop The World、對象分配規則、回收策略等與Serial收集器完全相同,兩者共用了相當多的程式碼。
Parallel Scavenge收集器
Parallel Scavenge收集器是一個新生代搜集器,主要採用複製演算法,與ParNew類似。但關注點與其他搜集器不同,目標是達到一個可控的吞吐量。
Serial Old收集器
Serial Old是Serial收集器的老年代版本,同樣是一個單執行緒收集器,使用標記-整理演算法。運作圖同Serial搜集器。
Parallel Old收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多執行緒和「標記-整理」演算法。在JDK 1.6中才開始提供。
CMS收集器
CMS(Concurrent Mark and Sweep 並發-標記-清除),是一種以獲取最短回收停頓時間為目標的收集器。基於並發、使用標記清除演算法,只針對老年代進行垃圾回收。
CMS收集器工作時,儘可能讓GC執行緒和用戶執行緒並發執行,以達到降低STW時間的目的。
整個操作步驟分為四步:初始標記(CMS initial mark)、並發標記(CMS concurrent mark)、重新標記(CMS remark)、並發清除(CMS concurrent sweep)。
在上圖過程中,初始標記和重新標記都會觸發「Stop The World」。
初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,在Java7中是單執行緒,在Java8以後可採用多執行緒。
發標記階段GC執行緒和應用執行緒並發執行,初始標記出來的存活對象,然後繼續遞歸標記這些對象可達的對象。
重新標記階段則是為了修正並發標記期間,因用戶程式繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短。
優點:並發收集、低停頓。
缺點:對CPU資源非常敏感、無法處理浮動垃圾、標記-清除演算法導致的空間碎片。
G1收集器
G1(Garbage-First)是一款面向服務端應用的垃圾收集器。支援新生代和老年代空間的垃圾收集。
該收集器可充分利用CPU和硬體縮短STW的時間,還具有「整合空間」、「可預測停頓」等特點。比如,可建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒。
使用G1收集器時,Java堆的記憶體布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合。
G1會跟蹤各個Region裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後台維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的來由)。
G1收集器的運作大致可劃分為以下幾個步驟:初始標記(Initial Marking)、並發標記(Concurrent Marking)、最終標記(Final Marking)、篩選回收(Live Data Counting and Evacuation)。
整個流程來看,前幾個步驟與CMS的流程很相似。同樣的在初始標記和最終標記的過程中都會觸發「Stop The World」。
其中,篩選回收階段其實也可以做到與用戶程式一起並發執行,但是因為只回收一部分Region,時間是用戶可控制的,而且停頓用戶執行緒將大幅度提高收集效率。
ZGC收集器
ZGC(Z Garbage Collector),是一款可伸縮、低延遲、並發垃圾回收器。在Java11中引入,應用Linux64位系統。
其旨在實現以下幾個目標:停頓時間不超過10ms、停頓時間不隨heap大小或存活對象大小增大而增大、可以處理從幾百兆到幾T的記憶體大小。
ZGC將記憶體劃分為多個區域,也稱為ZPage。ZPages可以動態創建和銷毀。它們也可以動態調整大小(與G1 GC不同),是2 MB的倍數。以下是堆區域的大小組:Small (2 MB)、Medium (32 MB)、Large (N * 2 MB)。
ZGC堆可以多次出現這些堆區域。中型和大型區域是連續分配的,如下圖所示:
與其他GC不同,ZGC的物理堆區域可以映射到更大的堆地址空間(其中可以包括虛擬記憶體)。
ZGC的執行過程包括:標記(初始標記、並發標記、邊緣情況處理)、重新定位(查找重新定位塊、根引用重新定位並更新、並發定位其他對象並存儲新舊地址映射)、重新映射。
其中標記中的初始標記和邊緣情況處理會引發「Stop The World」,重新定位中的「根引用重新定位並更新」也會引發「Stop The World」。
其中重新映射流程圖如下:
ZGC打算以較短的應用程式暫停時間來支援大堆大小。為了實現此目標,它使用了包括彩色64位指針,負載屏障,重定位和重新映射的技術。
小結
本文介紹了場景的垃圾收集器以及相關的概念,屬於較深層次的內容,針對這些內容還可以進一步進行橫向或縱向拓展。
有朋友在評論區問,學這些有底層什麼用?當然我們不僅僅是為了面試,就拿關於JVM結構及Java8 JVM記憶體結構變動來說吧。
昨天在部署一個比較大的項目時就出現「java.lang.OutOfMemoryError: Metaspace」異常,如果學習了之前的相關內容可以很輕易的定位到是因為JVM設置了Metaspace的上限參數,並且參數值設置小導致的。
最後,《面試官系列》正在持續更新,歡迎關注公眾號「程式新視界」,獲得最新內容。
原文鏈接:《面試官,不要再問我「Java 垃圾收集器」了》
《面試官》系列文章:
—