2014-08-28

Java Performance - Tuning the JVM step by step - 2


Book


Addison Wesley - Java Performance (2012)
Chapter 7 - Tuning the JVM step by step









前情提要
上一篇跑下來,我們首先決定了對系統效能的要求,選擇好JVM的佈屬方式、使用的JVM Runtime。
接著我們首先找出 application的 Live Data Size,根據Live Data Size 調整出初期的記憶體設定
然後就是tuning latency,latency主要是GC造成的,所以tuning的重點在於減少GC的頻率或是持續時間,調整YoungGen的大小來控制 minor GC的頻率與持續時間,然後調整OldGen的大小來控制OldGen/FullGC的頻率,並且在這過程中可能會考慮改為使用CMS GC。


Setp 6: Tune Application Throughput
- Throughput對每個application的定義不同,所以請先修改你的 application或測試程式,讓他們有辦法算出得到throughput。


  • CMS throughput tuning

- 如果在上一步切換成使用CMS GC 的話
CMS GC調整throughput的重點在於降低GC造成的CPU的使用,可能的做法如下:
    - 降低minor GC的頻率 >>> 加大YoungGen
    - 降低CMS Cycle或FullGC的頻率 >>> 加大OldGen或是調整eden或survivor 的空間,讓promote rate下降

* 調整過程,注意所有的GC加起來的時間,1%~3%以內算是很不錯的範圍,最糟不要超過10%



  • Throughput Garbage Collector Tuning

目標:減少FullGC的次數

Throughput GC預設會啟用 "adaptive sizing" ,這功能讓它可以自動調整YoungGen/OldGen的大小,adaptive sizing可以適用大部份的情況,但若想壓榨出更多的throughput,手動調整有可能可以再進一步得到更多的throughput。

-XX:-UseAdaptiveSizePolicy  - 用來停用adaptive sizing(注意 "-Use"代表停用
 - adaptive sizing 預設是啟用的
-XX:+PrintAdaptiveSizePolicy  - 設定印出更多的 survived/promoted/overflow 的資訊

PrintAdaptiveSizePolicy範例:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
   survived: 224408984
   promoted: 10904856
   overflow: false
* 224408984 是 survivor data size, 10904856 是 promoted bytes,false: 沒發生overflow

tuning的方式:

  1. 先找到一個steady state時發生的Full GC
  2. 首先確保OldGen的size 至少要 Live Data Size 的 1.5倍
  3. 接著觀察minor GC,看有沒有 survivor space overflow,如果有的話就加大survivor space的size。接著我們會再探討survivor space的tuning方式


  • Tuning Survivor Spaces

目標:儘可能讓只有需要長期存活的物件被promote到oldGen,減少FullGC


  1. 觀察survivor log,在兩次FullGC中間的minor GC裡找到survived的最大值
  2. 根據找到的survived最大值調整survivor space的大小
    *注意,survivor space的大小改變可能會間接造成eden的改變;若YoungGen的size改變的話,OldGen也會改變,所以調整時要注意它們之間的連動。理想狀態是讓survivor space變大而不改變其他space,但是這會需要更多的記憶體,假設真的沒辦法的話,另一種可能是調整 TargetSurvivorRatio,這樣可以在不改變其他space的size的情況下,讓survivor space有更多可用的空間

範例:
原設定如下:
-Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6 -XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy -XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:-PrintAdaptiveSizePolicy
 *以上設定可得:
  heap total 13g   YG = 4g   OG = 13-4 = 9g
  survivor size =  4/(6+2) == 0.5g(each)
  eden size = 4-(0.5*2) = 3g
  預設的 TargetSurvivorRatio == 50%,也就是說survived size 超過256MB就會promote

 若從觀察中發現最大的 survived size 是 473MB
   - 每個 survivor space要到 473 / 50% = 946MB ~= 1G 才會夠
   - 因為eden的大小不想改變,因此YoungGen總大小要變成 5g才夠: -Xmn5g
   - 因為YoungGen總大小變5g了,所以SurvivorRatio要變: -XX:SurvivorRatio=3
     (survivor = 5/(3+2) == 1g, eden = 5-(1*2) == 3g)
   - OldGen size不想改變:  -Xmx14g -Xms14g
新的設定如下:
-Xmx14g -Xms14g -Xmn5g -XX:SurvivorRatio=3 -XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy -XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:-PrintAdaptiveSizePolicy
*以上設定假設你有更多的記憶體可以給heap
*若無更多記憶體給heap,另一個方法可以設定 TargetSurvivorRatio == 90%,但請進一步確認不會有突然allocate大量object 的情況,不然可能會造成overflow


  • Tuning Parallel GC Threads
之前有提到可以透過以下設定GC可使用的thread數量
-XX:ParallelGCThreads=<n>  - 設定GC使用的thread數量

ParallelGCThreads預設值受到  Runtime.availableProcessors() 的影響
假設  P = Runtime.availableProcessors()
ParallelGCThreads = P <=8? P: 5/8p

若同一台機器有超過一個application的話,可能會需要設定 ParallelGCThreads,以防止單一個JVM gc影響到同一台機器的其他程式。



  • Deploying on NUMA Systems

若你的JVM運行於支援NUMA的系統上,可以考慮加上   -XX:+UseNUMA  以得到更好的效能
參考

Edge case

某些極端的情況,tuning的方向可能會與前述差很多:


  1. 如果application常常會一次allocate 大量的object,但是卻只有很少的object會長期存活,這種情況適合將YoungGen調大,大於OldGen
  2. 有些application 每次promote都很少,那其實OldGen可以不必比Live Data Size大多少
  3. 有些需要低latency的application,若使用CMS GC,可以考慮將YoungGen調小,讓minor GC更常發生,但是每次minor GC的時間更短,配合大一點的OldGen讓CMS Cycle更少發生,有可能因此latency反而較低

其他額外的Performance相關Command line



  • -XX:+AggressiveOpts

AggressiveOpts這個Option可以用來設定讓JVM使用一些新的performance option,這些option可能還不夠穩定,但是可以得到更好的效能,如果你可以接受一些小風險換來效能的增長的話,可以考慮使用這個Option。

其他可能的設定:
-XX:+DoEscapeAnalysis  - JDK6U23 之後預設啟用
 - escape指的是若某個Object可以給多個thread存取的話,代表這個 object是 escaped
 - Escape analysis可以分析object的使用,如果是從生到死都 non-escaped 的object的話,JVM可以對這個Object的使用做更多的最佳化,以得到更好的效能
-XX:+UseBiasedLocking  - 在 JDK6預設啟用
 - 同一個object會優先被前一個lock此object的thread取用,以降低lock的overhead
-XX:+UseLargePages  - JVM使用 TLB: translation lookaside buffer 來快取 virtual memory與實際 memory的mapping
 - UseLargePages 可以支援更大的TLB,降低TLB missing
- Linux可能不一定支援此Option [參考]
- Windows需要額外的設定才能使用Large Page [參考]
-XX:LargePageSizeInBytes=<n>[g|m|k]  - 在Solaris下,可以使用此Option來指定page size


其他

GCViewer:  可用來幫助分析 GC log  [GCViewer]



沒有留言 :

張貼留言