轉發Java GC - 垃圾回收機制
1、簡介
對於Java developer來說,了解JVM GC工作原理能夠幫助我們開發出更優秀的應用,同時在處理JVM瓶頸時能夠更加自由。在最近一年的應用開發中能體會到這些知識帶來的好處,並且讓我們的應用在較大規模的並發時能夠良好的工作。
本文部分知識和圖片來源於書籍《Java Performance》 - Charlie Hunt & Binu John 著,該書全面講解了Java 應用的性能分析、優化點與JVM原理等知識,本文(以及稍候的一些文章)只包含 GC collector 的部分知識,另外也只是針對常用的HotSpot JVM,當然很多知識是相通的。
2、 JVM Overview
上圖為JVM的概要構架,主要分為 VM Runtime、GC Collector以及 JIT Compiler,其中我們能通過配置幹預的只有GC 和 JIT(通過-server 和 -client選擇不同的 JIT Compiler,其中-server在啟動完成後具有更好的性能,但是花費啟動時間稍長些 -- 雖然我們沒明顯感覺)。
下圖則是JVM中內存分區模型,即分為 Heap area, method area, VM stack, native method stack和PC register。
本文只關註GC collector所涉及的Heap area 和 Method area, 其他的可另外了解。
3、Garbage Collections
在GC 中,最 ”引人入勝“ 的是 "stop-the-world",它在目前所實現的GC算法中都將出現,它出現的時候JVM 所承載的應用程序將停止執行,也就是說我們的應用中的所有線程將停止響應,直到 "stop-the-world" 完成工作。
HotSpot VM把 Heap Area分為 young generation 和 old generation兩個物理區域,也就是我們常翻譯為:年輕代和年老代。它們有以下特征:
1. 年輕代:大部分對象在這裏分配,通常來說會進行比較小但是比較頻繁的回收,花費時間較短。
2. 年老代:內存相對較大,對象會相對保留比較長的時間,大對象也一般分配在此,占用空間上升緩慢並且回收頻率低,"stop-the-world" 會在它回收時發生,花費較長的時間。
如下圖中,對象在年輕代分配,經過一些時間後可能會被移到年老代。其中的permanent generation為持久區,主要存放元數據如class data structures, interned strings等信息。GC 重點在於 young gen 和 old gen的回收。
3.1 Young generation
Young generation 有更細的劃分,分為 Eden 和 2個survivor space(即幸存區) 如下圖:
1. The eden: 大部分新對象在這裏分配,有些大對象直接在年老區分配,這個區域進行回收後很多情況下會變空。
2. The two survivor spaces:Eden 區滿時會將仍然存活的對象復制至其中一個survivor區,當這個區又滿時將復制至另外一個survivor區,如下圖:
3.2 Garbage Collectors
HotSpot 目前有三種可用的回收器: Serial GC, Parallel GC 和 Concurrent Mark-Sweep(CMS GC),回收類型分為mirror GC 和 major GC(也叫做Full GC),mirror GC針對young generation,major GC針對 old generation。
註:查詢應用使用的垃圾回收與內存分配情況使用:jmap -heap [PID],其中PID為進程ID,稍候的文章會可能會有示例。
3.2.1 Serial GC
當Java 應用啟動時如果加入-client 參數則使用這個回收器(如果不指定會根據官方的判斷方法(如CPU核數,內存大小等)來決定使用 -server還是 -client,詳情請查詢官方資料),在目前的硬件配置來說,指定-server是大部分場景所需。
該回收器的特點是單線程回收(在這個多核時代明顯很少被選擇),mirro GC和major GC都需要暫停應用(即stop-the-world)
,如下圖:
3.2.2 Parallel GC
在parallel GC 中,minor 和 full GC 都是並行的,可使用多核並行回收,可指定使用多少線程,能很好改善應用吞吐量,一般的機器配置中,如果不指定所使用的GC collector,一般默認為 Parallel GC,它符合大多數應用場景。如下圖:
3.2.3 CMS GC
該回收器的出現主要應對低響應延時的應用場景,即 stop-the-world 一旦發生,應用將停止響應直到它工作完成,這情況在很多WEB、電信或銀行應用中尤為關鍵。雖然 major GC很少發生,但是一旦發生將耗時較長,特別是在Heap很大的時候(也就是我們為什麽不能讓Heap分配很大的原因,適當即可,可有效分散GC暫停的時間使得不會感到明顯卡頓)。
CMS GC 旨在減少應用的響應時間(但會犧牲一些吞吐量),由於延長了GC的工作周期,所以有更大的heap區要求(在marking pause階段仍然允許分配內存),它同時具有更復雜的算法,CMS GC在管理 young generation方面與 Serial/Parallel GC一樣(通過參數指定,稍候的文章中會有詳解),在管理 old generation 采用2個周期來回收以減少應用暫停時間,如下圖:
大致動作描述分為:
1. Initial mark,標記在old generation之外的所有可直接抵達的對象(如靜態對象、線程棧等)。
2. Concurrent marking phase,在這個階段標記所有可抵達的(直接或間接)存活對象,在這個階段應用是不需要停止的,標記線程和應用線程同時工作。
3. Concurrent pre-cleaning為了減輕Remark pause步驟的工作,如訪問在making phase階段被更改的引用,雖然Remark pause還是會做這工作,但是它能顯著地減少Remark pause的持續時間。
4. Remark pause,由於應用工作時會不停地更新引用,所以最後還是需要暫停應用來徹底標記,這步驟重新檢查前一步驟(Concurrent marking phase)到暫停這時間段內更新的引用。
5. Concurrent sweeping phase,這步驟已經知道了所有在old generation存活的對象,將重新整理內存和釋放不可達對象(如下圖b所示),釋放後標識空閑空間,但他們是不連續的(Serial和parallel GC 則采用下圖a的壓縮方式),所以將導致在old generation分配內存代價比較昂貴。Marking pause階段能保證標記在這階段存活的對象,但不能保證所有不可達的對象能被清理,如果本次回收不能清理,則下次Full GC的時候將被回收。
最後談內存碎片問題:由於缺少內存壓縮將使得內存碎片化,CMS 提供壓縮周期,這周期也是stop the world,和serial、parallel GC 相似。使用-XX:CMSFullGCsBeforeCompaction參數指定full GC 多少次後進行內存壓縮,默認為0,一般可設置為1,稍候的文章中會有相關GC的參數詳解。
3.2.4 The Garbage-First GC
G1 GC 未來將作為 CMS GC 的一種替代(在Java 6 update 20 or later和 Java 7中才具有), 它可指定GC導致的應用停止時間,有不同的內存分布,提高應用實時性,但該GC 未經過大量的生產環境應用驗證,目前只作為實驗性特征使用。
關於這幾個GC行為對比如下圖:
4. 總結
JVM 調優主要在3點:
1. 選擇合適的 GC collector.
2. 指定合適的 heap area大小.
3. 指定young generation的比例(或大小),它影響到 Full GC發生的頻率以及應用暫停時間.
轉發Java GC - 垃圾回收機制