1. 程式人生 > >JVM實用參數(五)新生代垃圾回收

JVM實用參數(五)新生代垃圾回收

技術分享 針對 直接 關註 第一次 功能 忽略 找到 blank

JVM實用參數(五)新生代垃圾回收

原文鏈接 作者: PATRICK PESCHLOW ;譯者:嚴亮

本部分,我們將關註堆(heap) 中一個主要區域,新生代(young generation)。首先我們會討論為什麽調整新生代的參數會對應用的性能如此重要,接著我們將學習新生代相關的JVM參數。

單純從JVM的功能考慮,並不需要新生代,完全可以針對整個堆進行操作。新生代存在的唯一理由是優化垃圾回收(GC)的性能。更具體說,把堆劃分為新生代和老年代有2個好處:簡化了新對象的分配(只在新生代分配內存),可以更有效的清除不再需要的對象(即死對象)(新生代和老年代使用不同的GC算法)

通過廣泛研究面向對象實現的應用,發現一個共同特點:很多對象的生存時間都很短。同時研究發現,新生對象很少引用生存時間長的對象。結合這2個特點,很明顯 GC 會頻繁訪問新生對象,例如在堆中一個單獨的區域,稱之為新生代。在新生代中,GC可以快速標記回收”死對象”,而不需要掃描整個Heap中的存活一段時間的”老對象”。

SUN/Oracle 的HotSpot JVM 又把新生代進一步劃分為3個區域:一個相對大點的區域,稱為”伊甸園區(Eden)”;兩個相對小點的區域稱為”From 幸存區(survivor)”和”To 幸存區(survivor)”。按照規定,新對象會首先分配在 Eden 中(如果新對象過大,會直接分配在老年代中)。在GC中,Eden 中的對象會被移動到survivor中,直至對象滿足一定的年紀(定義為熬過GC的次數),會被移動到老年代。

基於大多數新生對象都會在GC中被收回的假設。新生代的GC 使用復制算法。在GC前To 幸存區(survivor)保持清空,對象保存在 Eden 和 From 幸存區(survivor)中,GC運行時,Eden中的幸存對象被復制到 To 幸存區(survivor)。針對 From 幸存區(survivor)中的幸存對象,會考慮對象年齡,如果年齡沒達到閥值(tenuring threshold),對象會被復制到To 幸存區(survivor)。如果達到閥值對象被復制到老年代。復制階段完成後,Eden 和From 幸存區中只保存死對象,可以視為清空。如果在復制過程中To 幸存區被填滿了,剩余的對象會被復制到老年代中。最後 From 幸存區和 To幸存區會調換下名字,在下次GC時,To 幸存區會成為From 幸存區。

技術分享圖片
https://blog.codecentric.de/files/2011/08/young_gc.png
上圖演示GC過程,黃色表示死對象,綠色表示剩余空間,紅色表示幸存對象

總結一下,對象一般出生在Eden區,年輕代GC過程中,對象在2個幸存區之間移動,如果對象存活到適當的年齡,會被移動到老年代。當對象在老年代死亡時,就需要更高級別的GC,更重量級的GC算法(復制算法不適用於老年代,因為沒有多余的空間用於復制)

現在應該能理解為什麽新生代大小非常重要了(譯者,有另外一種說法:新生代大小並不重要,影響GC的因素主要是幸存對象的數量),如果新生代過小,會導致新生對象很快就晉升到老年代中,在老年代中對象很難被回收。如果新生代過大,會發生過多的復制過程。我們需要找到一個合適大小,不幸的是,要想獲得一個合適的大小,只能通過不斷的測試調優。這就需要JVM參數了

-XX:NewSize and -XX:MaxNewSize
就像可以通過參數(-Xms and -Xmx) 指定堆大小一樣,可以通過參數指定新生代大小。設置 XX:MaxNewSize 參數時,應該考慮到新生代只是整個堆的一部分,新生代設置的越大,老年代區域就會減少。一般不允許新生代比老年代還大,因為要考慮GC時最壞情況,所有對象都晉升到老年代。(譯者:會發生OOM錯誤) -XX:MaxNewSize 最大可以設置為-Xmx/2 .

考慮性能,一般會通過參數 -XX:NewSize 設置新生代初始大小。如果知道新生代初始分配的對象大小(經過監控) ,這樣設置會有幫助,可以節省新生代自動擴展的消耗。

-XX:NewRatio
可以設置新生代和老年代的相對大小。這種方式的優點是新生代大小會隨著整個堆大小動態擴展。參數 -XX:NewRatio 設置老年代與新生代的比例。例如 -XX:NewRatio=3 指定老年代/新生代為3/1. 老年代占堆大小的 3/4 ,新生代占 1/4 .

如果針對新生代,同時定義絕對值和相對值,絕對值將起作用。下面例子:

1 $ java -XX:NewSize=32m -XX:MaxNewSize=512m -XX:NewRatio=3 MyApp

以上設置, JVM 會嘗試為新生代分配四分之一的堆大小,但不會小於 32MB 或大於 521MB

在設置新生代大小問題上,使用絕對值還是相對值,不存在通用準則 。如果了解應用的內存使用情況,設置固定大小的堆和新生代更有利,當然也可以設置相對值。如果對應用的內存使用一無所知,正確的做法是不要設置任何參數,如果應用運行良好。很好,我們不用做任何額外動作.如果遇到性能或OutOfMemoryErrors, 在調優之前,首先需要進行一系列有目的的監控測試,縮小問題的根源。

-XX:SurvivorRatio
參數 -XX:SurvivorRatio 與 -XX:NewRatio 類似,作用於新生代內部區域。-XX:SurvivorRatio 指定伊甸園區(Eden)與幸存區大小比例. 例如, -XX:SurvivorRatio=10 表示伊甸園區(Eden)是 幸存區To 大小的10倍(也是幸存區From的10倍).所以,伊甸園區(Eden)占新生代大小的10/12, 幸存區From和幸存區To 每個占新生代的1/12 .註意,兩個幸存區永遠是一樣大的..

設定幸存區大小有什麽作用? 假設幸存區相對伊甸園區(Eden)太小, 相應新生對象的伊甸園區(Eden)永遠很大空間, 我們當然希望,如果這些對象在GC時全部被回收,伊甸園區(Eden)被清空,一切正常.然而,如果有一部分對象在GC中幸存下來, 幸存區只有很少空間容納這些對象.結果大部分幸存對象在一次GC後,就會被轉移到老年代 ,這並不是我們希望的.考慮相反情況, 假設幸存區相對伊甸園區(Eden)太大,當然有足夠的空間,容納GC後的幸存對象. 但是過小的伊甸園區(Eden),意味著空間將越快耗盡,增加新生代GC次數,這是不可接受的。

總之,我們希望最小化短命對象晉升到老年代的數量,同時也希望最小化新生代GC 的次數和持續時間.我們需要找到針對當前應用的折中方案, 尋找適合方案的起點是 了解當前應用中對象的年齡分布情況。

-XX:+PrintTenuringDistribution
參數 -XX:+PrintTenuringDistribution 指定JVM 在每次新生代GC時,輸出幸存區中對象的年齡分布。例如:

1 Desired survivor size 75497472 bytes, new threshold 15 (max 15)
2 - age 1: 19321624 bytes, 19321624 total
3 - age 2: 79376 bytes, 19401000 total
4 - age 3: 2904256 bytes, 22305256 total

第一行說明幸存區To大小為 75 MB. 也有關於老年代閥值(tenuring threshold)的信息, 老年代閥值,意思是對象從新生代移動到老年代之前,經過幾次GC(即, 對象晉升前的最大年齡). 上例中,老年代閥值為15,最大也是15.

之後行表示,對於小於老年代閥值的每一個對象年齡,本年齡中對象所占字節 (如果當前年齡沒有對象,這一行會忽略). 上例中,一次 GC 後幸存對象大約 19 MB, 兩次GC 後幸存對象大約79 KB , 三次GC 後幸存對象大約 3 MB .每行結尾,顯示直到本年齡全部對象大小.所以,最後一行的 total 表示幸存區To 總共被占用22 MB . 幸存區To 總大小為 75 MB ,當前老年代閥值為15,可以斷定在本次GC中,沒有對象會移動到老年代。現在假設下一次GC 輸出為:

1 Desired survivor size 75497472 bytes, new threshold 2 (max 15)
2 - age 1: 68407384 bytes, 68407384 total
3 - age 2: 12494576 bytes, 80901960 total
4 - age 3: 79376 bytes, 80981336 total
5 - age 4: 2904256 bytes, 83885592 total

對比前一次老年代分布。明顯的,年齡2和年齡3 的對象還保持在幸存區中,因為我們看到年齡3和4的對象大小與前一次年齡2和3的相同。同時發現幸存區中,有一部分對象已經被回收,因為本次年齡2的對象大小為 12MB ,而前一次年齡1的對象大小為 19 MB。最後可以看到最近的GC中,有68 MB 新對象,從伊甸園區移動到幸存區。

註意,本次GC 幸存區占用總大小 84 MB -大於75 MB. 結果,JVM 把老年代閥值從15降低到2,在下次GC時,一部分對象會強制離開幸存區,這些對象可能會被回收(如果他們剛好死亡)或移動到老年代。

-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio
參數 -XX:+PrintTenuringDistribution 輸出中的部分值可以通過其它參數控制。通過 -XX:InitialTenuringThreshold 和 -XX:MaxTenuringThreshold 可以設定老年代閥值的初始值和最大值。另外,可以通過參數 -XX:TargetSurvivorRatio 設定幸存區的目標使用率.例如 , -XX:MaxTenuringThreshold=10 -XX:TargetSurvivorRatio=90 設定老年代閥值的上限為10,幸存區空間目標使用率為90%。

有多種方式,設置新生代行為,沒有通用準則。我們必須清楚以下2中情況:
1 如果從年齡分布中發現,有很多對象的年齡持續增長,在到達老年代閥值之前。這表示 -XX:MaxTenuringThreshold 設置過大
2 如果 -XX:MaxTenuringThreshold 的值大於1,但是很多對象年齡從未大於1.應該看下幸存區的目標使用率。如果幸存區使用率從未到達,這表示對象都被GC回收,這正是我們想要的。 如果幸存區使用率經常達到,有些年齡超過1的對象被移動到老年代中。這種情況,可以嘗試調整幸存區大小或目標使用率。

-XX:+NeverTenure and -XX:+AlwaysTenure
最後,我們介紹2個頗為少見的參數,對應2種極端的新生代GC情況.設置參數 -XX:+NeverTenure , 對象永遠不會晉升到老年代.當我們確定不需要老年代時,可以這樣設置。這樣設置風險很大,並且會浪費至少一半的堆內存。相反設置參數 -XX:+AlwaysTenure, 表示沒有幸存區,所有對象在第一次GC時,會晉升到老年代。
沒有合理的場景使用這個參數。可以在測試環境中,看下這樣設置會發生什麽有趣的事.但是並不推薦使用這些參數.

結論
適當的配置新生代非常重要,有相當多的參數可以設置新生代。然而,單獨調整新生代,而不考慮老年代是不可能優化成功的。當調整堆和GC設置時,我們總是應該同時考慮新生代和老年代。

在本系列的下面2部分,我們將討論 HotSpot JVM 中老年代 GC 策略,我們會學習“吞吐量GC收集器” 和 “並發低延遲GC收集器”,也會了解收集器的基本準則,算法和調整參數.

原創文章,轉載請註明: 轉載自並發編程網 – ifeve.com本文鏈接地址: JVM實用參數(五)新生代垃圾回收

JVM實用參數(五)新生代垃圾回收