深入理解G1
? ? ? ? G1是JVM歷史上具有里程碑意義的收集器,開創了垃圾收集可控暫停時間的停頓時間模型。從G1開始,垃圾收集器不再追求一次將整個堆清理干凈,而是追求可控的STW時間,以及在STW時間內盡可能高的內存回收速率。早期闡述Java的GC機制時,經常使用媽媽打掃房間的例子,這個例子說的是你在房間里吃瓜子,然后瓜子皮丟在地上,媽媽在打掃房間的過程中,必須在某個時刻限制你暫停吃瓜子(STW)一段時間,否則房間永遠沒有打掃完的時候。我當時看到這個例子時想到了兩點:1、即使我被暫停吃瓜子一段時間用來打掃干凈房間,可是之后我還是會繼續吃瓜子并將瓜子皮丟在地上,那么這片刻的房間打掃干凈是否有意義;2、媽媽不確定我會把瓜子皮丟在哪兒,那是不是意味著,房間越大,限制我暫停吃瓜子的時間就越長。
? ? ? ? 隨著64位JVM的出現,以及對更大的堆內存需求增長,傳統的垃圾收集器的弱點越來越明顯,尤其是隨著大數據基礎設施的發展,大堆是剛需,即使是最成熟的ParNew+CMS,面對大數據這種對象生存周期長,突然產生大量對象的場景也是hold不住,G1的產生為可控的STW、大堆提供了可能。
?
一、G1的特點
? ? ? ? 停頓時間模型是G1最大的特點,停頓時間模型的意思是在GC過程中支持指定STW時間大概率不超過M毫秒。為達到此目標,G1采用了面向局部收集的設計思路和基于Region的內存布局。基于Region的堆內存布局是達成這個目標的關鍵,GC時將Region作為回收的最小單元,G1收集器跟蹤每個Region的內存使用情況,根據歷史統計值估算回收此Region能夠獲得的內存空間大小和暫停時間大小,然后在后臺維護Region回收的優先級列表,GC時根據用戶設定的暫停時間,按照優先級列表中的順序,優先回收價值最大的Region。當然,在生產環境中,最大停頓時間的設置不能過于理想化,我們可以認為每次垃圾回收釋放的內存和最大停頓時間正相關。很小的停頓時間會導致每次垃圾回收釋放的堆內存很小,垃圾收集的速度跟不上分配器分配的速度,導致垃圾堆積,最終占滿整個堆觸發Full GC反而降低性能。
?
二、內存布局
? ? ? ? G1的內存布局是將堆分為多個大小相等,彼此獨立的Region,Region的大小根據啟動參數取值范圍為1MB~32MB,且應為2^N,每個Region都可以作為新生代Eden區,Survivor區,或者老年代空間,Region中海油一類專門放置大對象的Humongous Region。G1的內存布局如圖:
?
三、G1的其他關鍵概念
? ? ? ? Remembered Set:由于G1的面向局部收集策略,所以在回收垃圾時出現的跨Region引用對象問題,為解決此問題引入Remembered Set記錄別的Region指向自己的指針。由于G1的Region數量比傳統收集器的分代數量多很多,所以G1收集器要比其他傳統垃圾收集器有更高的內存占用負擔。
? ? ? ? Card Table:G1引入的數據結構,是Remembered Set的一種實現,用于記錄非收集區域是否存在指向收集區域的指針,類似Hash表。
? ? ? ? Collect Set:在垃圾收集階段,由G1垃圾回收器選擇的待回收的Region集合。
? ? ? ? 寫屏障:指的是改變內存值的時候額外執行一些操作。Java使用的分代垃圾回收基本都會用到寫屏障,主要處理跨代引用的問題,比如將新生代對象的引用寫入老年代對象進行寫操作時,如果此時垃圾回收線程也在工作,那么需要仔細處理老年代對象對新生代對象的引用,避免出現引用沒有被正確及時的探測到,進而存活對象被過早回收的問題。
?
四、G1進行GC的流程
? ? ? ? G1工作是有兩種模式,分別是Young Gc模式和Mixed GC模式,兩種都是要STW的。Young GC模式和PareNew的非常像,就不詳細介紹了,主要區別就是Eden區和Survivor區不再是一整塊內存,而是由多個Region邏輯組成。Mixed GC回收時,不區分是新生代還是老年代,而是按照能從Region中回收的垃圾最多,回收收益大排序,組成回收集,對回收集中的Region進行回收,正常情況下新生代的Eden區都在回收范圍內。如果設置的暫停時間過短,可能會導致Mixed GC回收時來不及回收Old區。Mixed GC的工作流程如下:
?
? ? ? ? 整個過程分為兩個大的階段:標記階段和拷貝存活對象階段。標記階段的主要目標是掃描堆內存使用情況,估算每個Region的垃圾回收收益。拷貝存活對象階段決定回收哪些Region,將Region里的存活對象復制到空Region中,再清理掉整個舊Region的全部內存空間。具體過程分為以下四個步驟:
- 初始標記(Initial Marking):G1的這個階段與Minor GC同步完成的,此階段標記通過GC Roots能直接關聯到的對象(這個階段需要STW),之后將復制到新Region的Survivor區的對象掃描并標記為根;
- 并發標記(Concurrent Marking):對全堆的對象圖進行可達性分析,找出待回收對象。這個階段運行時間比較長,但是可以與應用程序并發進行,對象圖掃描完之后,還需要處理并發標記過程中有引用變動的對象;
- 重新標記(Final Marking):仍然需要一個STW階段,用于處理并發標記階段結束后仍然有引用變動的對象;
- 篩選回收(Cleanup):根據標記的可達對象,更新Region的統計數據,對各個Region的回收價值和成本進行排序,根據用戶設置的JVM暫停時間制定回收計劃,決定回收那一部分Region的存活對象復制到空的Region中,再清理掉整個舊的Region的全部空間。此過程設計到了存活對象的復制,需要STW。
?
五、G1的缺點
? ? ? ? 和CMS算法相比,G1優點很多,最主要的是從整體上是基于”標記-清除“算法實現的垃圾收集器,但從局部上,又是基于”標記-復制“算法實現,這標志著G1在運行期間不會產生內存碎片,有利于程序長時間運行。
? ? ? ? G1相比CMS的弱點也有不少,主要表現就是G1的Remembered Set對內存有更高的需求,某些極端情況下會達到堆空間的20%甚至更多,GC進行時垃圾回收器的執行負載較高,導致應用的吞吐量不如CMS高。目前,在小內存的堆上,CMS的表現大概率上仍然優于G1;在大內存的堆上,G1則更能發揮優勢,所以在小于6G~8G的堆上使用CMS可能有更好的性能,而在更大的堆上應該優先選擇G1。
?
參考資料
1、深入理解Java虛擬機-JVM高級特性與最佳實踐(第3版)
2、垃圾回收算法手冊
?
個人公眾號:
? ? ? ? 自己在軟件這個行業10來年的工作和學習歷程中犯了不少錯誤,也走了不少彎路,在此公眾號分享自己的成長心路和工作心得,希望給后來的從業者一個參照,不要再犯我犯過的錯誤,不要再走我走過的彎路。在此我也會分享工作中遇到的技術問題和自己研究技術的記錄與心得;在項目過程中遇到的風險和暴露于化解風險的過程和方法;在團隊管理過程中的心得和體會。后續會發布一系列專題技術文章,程序員成長系列文章,項目的系列文章,行業發展分析和展望的文章,甚至會包含婚戀和育兒的心得文章,我是個不專注卻熱愛生活的工程師!
總結
- 上一篇: android Telephony学习
- 下一篇: 对话bot语音输入交互竞品调研