鍍金池/ 教程/ Java/ 新生代垃圾回收
新生代垃圾回收
參數(shù)分類和即時(JIT)編譯器診斷
打印所有 XX 參數(shù)及值
GC 日志
JVM 類型以及編譯器模式
內(nèi)存調(diào)優(yōu)
CMS 收集器
吞吐量收集器

新生代垃圾回收

原文鏈接 作者: PATRICK PESCHLOW;譯者:嚴(yán)亮

本部分,我們將關(guān)注堆 (heap) 中一個主要區(qū)域,新生代 (young generation)。首先我們會討論為什么調(diào)整新生代的參數(shù)會對應(yīng)用的性能如此重要,接著我們將學(xué)習(xí)新生代相關(guān)的 JVM 參數(shù)。

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

通過廣泛研究面向?qū)ο髮崿F(xiàn)的應(yīng)用,發(fā)現(xiàn)一個共同特點:很多對象的生存時間都很短。同時研究發(fā)現(xiàn),新生對象很少引用生存時間長的對象。結(jié)合這 2 個特點,很明顯 GC 會頻繁訪問新生對象,例如在堆中一個單獨的區(qū)域,稱之為新生代。在新生代中,GC 可以快速標(biāo)記回收” 死對象”,而不需要掃描整個 Heap 中的存活一段時間的” 老對象”。

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

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

http://wiki.jikexueyuan.com/project/jvm-parameter/images/1.png" alt="" />

https://blog.codecentric.de/files/2011/08/young_gc.png

上圖演示 GC 過程,黃色表示死對象,綠色表示剩余空間,紅色表示幸存對象

總結(jié)一下,對象一般出生在 Eden 區(qū),年輕代 GC 過程中,對象在 2 個幸存區(qū)之間移動,如果對象存活到適當(dāng)?shù)哪挲g,會被移動到老年代。當(dāng)對象在老年代死亡時,就需要更高級別的 GC,更重量級的 GC 算法 (復(fù)制算法不適用于老年代,因為沒有多余的空間用于復(fù)制)

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

-XX:NewSize and -XX:MaxNewSize

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

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

-XX:NewRatio

可以設(shè)置新生代和老年代的相對大小。這種方式的優(yōu)點是新生代大小會隨著整個堆大小動態(tài)擴(kuò)展。參數(shù) -XX:NewRatio 設(shè)置老年代與新生代的比例。例如 -XX:NewRatio=3 指定老年代 / 新生代為 3/1。 老年代占堆大小的 3/4,新生代占 1/4 。

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

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

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

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

-XX:SurvivorRatio

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

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

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

-XX:+PrintTenuringDistribution

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

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

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

之后行表示,對于小于老年代閥值的每一個對象年齡,本年齡中對象所占字節(jié) (如果當(dāng)前年齡沒有對象, 這一行會忽略)。 上例中, 一次 GC 后幸存對象大約 19 MB, 兩次 GC 后幸存對象大約 79 KB,三次 GC 后幸存對象大約 3 MB 。 每行結(jié)尾,顯示直到本年齡全部對象大小。 所以, 最后一行的 total 表示幸存區(qū) To 總共被占用 22 MB 。 幸存區(qū) To 總大小為 75 MB , 當(dāng)前老年代閥值為 15,可以斷定在本次 GC 中,沒有對象會移動到老年代?,F(xiàn)在假設(shè)下一次 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 的對象還保持在幸存區(qū)中,因為我們看到年齡 3 和 4 的對象大小與前一次年齡 2 和 3 的相同。同時發(fā)現(xiàn)幸存區(qū)中, 有一部分對象已經(jīng)被回收, 因為本次年齡 2 的對象大小為 12MB ,而前一次年齡 1 的對象大小為 19 MB。最后可以看到最近的 GC 中,有 68 MB 新對象,從伊甸園區(qū)移動到幸存區(qū)。

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

-XX:InitialTenuringThreshold, -XX:MaxTenuringThreshold and -XX:TargetSurvivorRatio

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

有多種方式, 設(shè)置新生代行為,沒有通用準(zhǔn)則。我們必須清楚以下 2 中情況:

  1. 如果從年齡分布中發(fā)現(xiàn),有很多對象的年齡持續(xù)增長,在到達(dá)老年代閥值之前。這表示 -XX:MaxTenuringThreshold 設(shè)置過大
  2. 如果 -XX:MaxTenuringThreshold 的值大于 1,但是很多對象年齡從未大于 1。應(yīng)該看下幸存區(qū)的目標(biāo)使用率。如果幸存區(qū)使用率從未到達(dá),這表示對象都被 GC 回收,這正是我們想要的。 如果幸存區(qū)使用率經(jīng)常達(dá)到,有些年齡超過 1 的對象被移動到老年代中。這種情況,可以嘗試調(diào)整幸存區(qū)大小或目標(biāo)使用率。

-XX:+NeverTenure and -XX:+AlwaysTenure

最后,我們介紹 2 個頗為少見的參數(shù),對應(yīng) 2 種極端的新生代 GC 情況。設(shè)置參數(shù) -XX:+NeverTenure,對象永遠(yuǎn)不會晉升到老年代。當(dāng)我們確定不需要老年代時,可以這樣設(shè)置。這樣設(shè)置風(fēng)險很大, 并且會浪費至少一半的堆內(nèi)存。相反設(shè)置參數(shù) -XX:+AlwaysTenure,表示沒有幸存區(qū),所有對象在第一次 GC 時,會晉升到老年代。

沒有合理的場景使用這個參數(shù)。可以在測試環(huán)境中,看下這樣設(shè)置會發(fā)生什么有趣的事。但是并不推薦使用這些參數(shù)。

結(jié)論 適當(dāng)?shù)呐渲眯律浅V匾邢喈?dāng)多的參數(shù)可以設(shè)置新生代。然而,單獨調(diào)整新生代,而不考慮老年代是不可能優(yōu)化成功的。當(dāng)調(diào)整堆和 GC 設(shè)置時,我們總是應(yīng)該同時考慮新生代和老年代。

在本系列的下面 2 部分,我們將討論 HotSpot JVM 中老年代 GC 策略,我們會學(xué)習(xí) “吞吐量 GC 收集器” 和 “并發(fā)低延遲 GC 收集器”,也會了解收集器的基本準(zhǔn)則,算法和調(diào)整參數(shù)。

上一篇:GC 日志下一篇:吞吐量收集器