JVM 調優 —— 新生代 Survivor 空間不足

零. 新生代調優規律
增大新生代空間, Minor GC 頻率減少, Minor GC 時間上升。 減少新生代空間, Minor GC 頻率上升, Minor GC 時間下降


一. 新生代典型問題

先看一段 GC 日誌:新生代使用 ParNew, 老年代使用 CMS 

{Heap before GC invocations=0 (full 0):
 par new generation   total 943744K, used 838912K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
  eden space 838912K, 100% used [0x0000000757000000, 0x000000078a340000, 0x000000078a340000)
  from space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
  to   space 104832K,   0% used [0x00000007909a0000, 0x00000007909a0000, 0x0000000797000000)
 concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
 concurrent-mark-sweep perm gen total 159744K, used 38069K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
2016-01-19T14:15:34.532+0800: 13.812: [GC2016-02-19T14:15:34.532+0800: 13.812: [ParNew
Desired survivor size 53673984 bytes, new threshold 1 (max 6)
- age   1:   55521392 bytes,   55521392 total
: 838912K->54474K(943744K), 0.0914620 secs] 838912K->54474K(2504320K), 0.0916240 secs] [Times: user=0.67 sys=0.06, real=0.09 secs]
Heap after GC invocations=1 (full 0):
 par new generation   total 943744K, used 54474K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
  eden space 838912K,   0% used [0x0000000757000000, 0x0000000757000000, 0x000000078a340000)
  from space 104832K,  51% used [0x00000007909a0000, 0x0000000793ed2ae0, 0x0000000797000000)
  to   space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
 concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
 concurrent-mark-sweep perm gen total 159744K, used 38069K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
}
{Heap before GC invocations=1 (full 0):
 par new generation   total 943744K, used 893386K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
  eden space 838912K, 100% used [0x0000000757000000, 0x000000078a340000, 0x000000078a340000)
  from space 104832K,  51% used [0x00000007909a0000, 0x0000000793ed2ae0, 0x0000000797000000)
  to   space 104832K,   0% used [0x000000078a340000, 0x000000078a340000, 0x00000007909a0000)
 concurrent mark-sweep generation total 1560576K, used 0K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
 concurrent-mark-sweep perm gen total 159744K, used 53249K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
2016-01-19T14:15:41.943+0800: 21.222: [GC2016-02-19T14:15:41.943+0800: 21.223: [ParNew
Desired survivor size 53673984 bytes, new threshold 1 (max 6)
- age   1:  107256200 bytes,  107256200 total
: 893386K->104832K(943744K), 1.2389070 secs] 893386K->210614K(2504320K), 1.2391870 secs] [Times: user=2.89 sys=0.35, real=1.24 secs]
Heap after GC invocations=2 (full 0):
 par new generation   total 943744K, used 104832K [0x0000000757000000, 0x0000000797000000, 0x0000000797000000)
  eden space 838912K,   0% used [0x0000000757000000, 0x0000000757000000, 0x000000078a340000)
  from space 104832K, 100% used [0x000000078a340000, 0x00000007909a0000, 0x00000007909a0000)
  to   space 104832K,   0% used [0x00000007909a0000, 0x00000007909a0000, 0x0000000797000000)
  concurrent mark-sweep generation total 1560576K, used 105782K [0x0000000797000000, 0x00000007f6400000, 0x00000007f6400000)
 concurrent-mark-sweep perm gen total 159744K, used 53249K [0x00000007f6400000, 0x0000000800000000, 0x0000000800000000)
}



可以明顯看出上述 GC 日誌包含兩次 Minor GC。 注意到第二次 Minor GC 的情況, 日誌打出 "Desired survivor size 53673984 bytes", 但是卻存活了 "- age   1:  107256200 bytes,  107256200 total" 這麼多。 可以看出明顯的新生代的 Survivor 空間不足。正因爲 Survivor 空間不足, 那麼從 Eden 存活下來的和原來在 Survivor 空間中不夠老的對象佔滿 Survivor 後, 就會提升到老年代, 可以看到這一輪 Minor GC 後老年代由原來的 0K 佔用變成了 105782K 佔用, 這屬於一個典型的 JVM 內存問題, 稱爲 "premature promotion"(過早提升)。

"premature promotion” 在短期看來不會有問題, 但是經常性的 "premature promotion”, 最總會導致大量短期對象被提升到老年代, 最終導致老年代空間不足, 引發另一個 JVM 內存問題 “promotion failure”(提升失敗: 即老年代空間不足以容乃 Minor GC 中提升上來的對象)。  “promotion failure” 發生就會讓 JVM 進行一次 CMS 垃圾收集進而騰出空間接受新生代提升上來的對象, CMS 垃圾收集時間比 Minor GC 長, 導致吞吐量下降、 時延上升, 將對用戶體驗造成影響。


二. 新生代調優建議
對於上述的新生代問題, 如果服務器內存足夠用, 建議是直接增大新生代空間(如 -Xmn)。如果內存不夠用, 則增加 Survivor 空間, 減少 Eden 空間, 但是注意減少 Eden 空間會增加 Minor GC 頻率, 要考慮到應用對延遲和吞吐量的指標最終是否符合。

要增大多少 Survivor 空間? 需要觀察多次 Minor GC 過程, 看 Minor GC 後存活下來的對象大小, 最終確定 Survivor 的合適大小。 整個調優過程可能需要幾次調整, 才能找到比較合適的值。調整幾次後, 如果內存還是不夠用, 就要需要考慮增大服務器內存, 或者把負載分擔到更多的 JVM 實例上。

Survivor 空間計算公式: survivor 空間大小 = -Xmn[value] / (-XX:SurvivorRatio=<ratio> + 2)  



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章