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

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

本部分,咱們將關注堆(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的次數),會被移動到老年代。jvm

基於大多數新生對象都會在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 倖存區。性能


上圖演示GC過程,黃色表示死對象,綠色表示剩餘空間,紅色表示倖存對象學習

總結一下,對象通常出生在Eden區,年輕代GC過程當中,對象在2個倖存區之間移動,若是對象存活到適當的年齡,會被移動到老年代。當對象在老年代死亡時,就須要更高級別的GC,更重量級的GC算法(複製算法不適用於老年代,由於沒有多餘的空間用於複製)測試

如今應該能理解爲何新生代大小很是重要了(譯者,有另一種說法:新生代大小並不重要,影響GC的因素主要是倖存對象的數量),若是新生代太小,會致使新生對象很快就晉升到老年代中,在老年代中對象很難被回收。若是新生代過大,會發生過多的複製過程。咱們須要找到一個合適大小,不幸的是,要想得到一個合適的大小,只能經過不斷的測試調優。這就須要JVM參數了優化

-XX:NewSize and -XX:MaxNewSize
就像能夠經過參數(-Xms and -Xmx) 指定堆大小同樣,能夠經過參數指定新生代大小。設置 XX:MaxNewSize 參數時,應該考慮到新生代只是整個堆的一部分,新生代設置的越大,老年代區域就會減小。通常不容許新生代比老年代還大,由於要考慮GC時最壞狀況,全部對象都晉升到老年代。(譯者:會發生OOM錯誤) -XX:MaxNewSize 最大能夠設置爲-Xmx/2 .code

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

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

若是針對新生代,同時定義絕對值和相對值,絕對值將起做用。下面例子:
$ 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時,輸出倖存區中對象的年齡分佈。例如:
Desired survivor size 75497472 bytes, new threshold 15 (max 15)
- age 1: 19321624 bytes, 19321624 total
- age 2: 79376 bytes, 19401000 total
- 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 輸出爲:

Desired survivor size 75497472 bytes, new threshold 2 (max 15)
- age 1: 68407384 bytes, 68407384 total
- age 2: 12494576 bytes, 80901960 total
- age 3: 79376 bytes, 80981336 total
- 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實用參數(五)新生代垃圾回收

相關文章
相關標籤/搜索