Book
Addison Wesley - Java Performance (2012)
Chapter 7 - Tuning the JVM step by step
開始前
- 最好可以先解一下第三章的GC或JIT的部份
- 每個部份的調整可以分開來看,但是了解整個process會有更多幫助
方法流程
假設一個軟體的執行分成三個phase:
- initialization: startup, initial some data structures
- steady state: the normal work time
- optional phase: maybe generate report or benchmark
其中我們最感興趣的會是 steady state,大部份的調整是針對steady state。
調整過程中,環境與真正production環境越接近,結果越真實。
大至過程如下,過程中若有任何一個phase的目標無法達成,可能會回到上一步或是更早的phase,甚至是重新評估一開始所訂的目標,或是對程式做修改,然後再重覆調整,直到所有的目標都可以滿足。整個過程的圖可以參考圖 7-1 。
- Prioritize application systemic requirement: 主要因為不同的需求可能是互斥的,所以會需要先排出優先順序,後續所有的步驟的目標都是要滿足這個需求,如果無法滿足,可能就要修正需求,或是修改程式。
- choose JVM deployment model: single JVM or multiple JVM
- choose JVM runtime
- tuning application memory footprint
- tuning latency
- tuning throughput
Step 1: Application Systemic Requirements
可能的requirement如下表:
Availability | application持續處於可使用的狀態的能力,一般可以透過將application佈屬至更多的JVM來達成較好的availability,但是如此也會直接地增加management成本。 |
Manageability | 維持與監控application的運作所需的人、物力,一般來說,越少的JVM佈屬,越低的管理成本與需求。 |
Throughput | 每單位時間可處理的工作量,一般情況,throughput的增加可能會伴隨著latency或memory footprint的增加 |
Latency and Responsiveness | 收到request到處理完、回應所需的時間。一般來說,降低latency可能造成throughput的下降,或是增加memory footprint。 |
Memory Footprint | 所需的memory用量,增加可用的memory通常可以改善其他的問題,但是memory受限於實體機器的記憶體總量。 |
Startup Time | 程式啟始所需要的時間。可能跟起動程式時所需要load的class量、初始化的方式、或是所選的JVM runtime有關… |
針對上述requirement根據重要性給予排序,此過程可以邀請較高層級的管理者來參與,以幫助訂出最重要的需求
Step 2: Choose JVM deployment Model
可能的選項: Single JVM、Multiple JVM
| Single JVM | Multiple JVM |
Availability | 差 | 佳 |
Manageability | 成本較低 | 成本相對高 |
Memory footprint | 較低的usage overhead | 每個單一JVM可用的memory量較少 |
Throughput | - | scale-out 可能可以增加throughput |
Latency | - | 每個單一JVM的heap可能比較小,gc造成的latency有可能會因此降低 |
Step 3: Choose JVM runtime
- client or server (or tiered) runtime
client | 啟動較快,相對較小的記憶體用量,相對較快的JIT(但最佳化的效果相對差)
64bit JVM 沒有client runtime |
server | 啟動較慢,JIT可產生更佳的的結果(但是相對需要更多的時間處理) |
tiered | 號稱結合 client與server runtime的好處,可以考慮用來取代client runtime。
需 J6U25+ or J7
command line: -server-XX:+TieredCompilation |
考量的重點:
- 64bit JVM 無 client runtime
- 所使用的 3rd party 軟體是否可與64bit JVM相容
- 所使用的 JNI程式是否有64bit版
- 是否需要更多的heap memory?
根據不同的作業系統,與不同的記憶體需求,表7-1整理了可能的決定方式
在這個階段會先決定一個初始GC方式,大至來說,書上是直接建議先使用throughput garbage collector (使用 command line option:
-XX:+UseParallelOldGC ),後續的tuning中,也有可能會需要切換成concurrent GC,這會有後面的篇章再詳述。(有關各GC的特性,詳細請參閱CH3)
番外篇: GC Tuning Fundamentals
接下來的Tuning主要是從GC的tuning來調整throughput、latency、memory footprint,加強任何一項都可能使另一項或兩項的效能下降(此時
Step 1 的決定就可以幫助我們做取捨),這邊我們先針對GC Tuning的基本做一些介紹
- Command Line Options and GC Logging
要tuning GC,看gc log是必需的,gc log需要手動加上 command line options才會輸出,overhead不大但卻可提供有用的資訊,建議即使 Production的環境也將log打開。
指令 | 效果 |
-Xloggc:<file> | 指定輸出的log檔案
此為輸出log所需的最小設定之一 |
-XX:+PrintGCTimeStamps | 印出log的timestamp
格式是JVM啟動之後所經過的時間(秒)
此為輸出log所需的最小設定之一 |
-XX:+PrintGCDetails | 印出GC的統計資訊
此為輸出log所需的最小設定之一 |
-XX:+PrintGCDateStamps | 印出日期、時間格式的timestamp |
-XX:+PrintGCApplicationStoppedTime | 輸出GC safepoint的時間
tuning latency 所需 |
-XX:+PrintGCApplicationConcurrentTime | 輸出兩次GC safepoint經過的時間
tuning latency 所需 |
-XX:+PrintSafepointStatistics | 輸出所有的JVM safepoint事件的資訊,搭配-XX:+PrintGCApplicationStoppedTime可用來濾出GC造成的 safepoint
tuning latency 所需 |
- gc log的解讀方式 (接下來的tuning大多需要從GC log了解狀況)
45.152: [GC [PSYoungGen: 295648K->32968K(306432K)] 296198K->33518K(1006848K), 0.1083183 secs] [Times: user=1.83 sys=0.01, real=0.11 secs] |
以上log代表
- 這個GC是在JVM啟動之後過了 45.152 秒後發生的
- 這是個minor GC,其中的PSYoungGen這個gc(這是個Young Generation gc),讓YoungGeneration的space size從 從295648K減少成 32968K,而這個Young Generation的總空間是 306432K (Young Generation的總空間 ==> eden space 加上兩個 survivor space)
- 這個JVM的 Young Generation + Old Generation的heap size從總共有 1006848K,並且GC後,size從 296198K 變成 33518K。
- 這個GC共花了 0.1083183秒
- 這個GC共花了user mode的CPU time約 1.83秒,system mode約0.01秒cpu time,而真實時間約是 0.11秒
進一步的解讀
- 因為eden會在gc後清空,所以可以知道此GC完之後,survivor space的size是 32968K
- Old generation 的總空間則是 1106848 - 306432 = 700416
- 296198 - 295648 = 550K == GC前Old generation 的size
- 33518-32968 = 550K == GC後Old generation的size
- 由前兩項可知此次的minor GC,並沒有任何object被promote到 Old Generation
Step 4: Determine Memory Footprint
計算出此application的
Live Data Size。 Live Data Size指的是在這個application處於Steady state時,一群長期存活著的object 所佔的空間,代表這個application處於steady state時,至少會需要這麼多的記憶體;換個角度說,Live Data Size指的是當application處於steady state時,GC之後的heap size。
deploy model會影響JVM可使用的memory量,若選擇 Single JVM的model,那JVM可以使用接近所有的實體記憶體;反之,若選擇多個JVM的佈屬方式,則每個JVM可以使用的實體記憶體會較小
指令 | 效果 |
-Xms
| 指定 java heap(YoungGen+OldGen)的初始總量 |
-Xmx | 指定java heap的最大值,java heap不會超過這個值 |
-XX:NewSize=<n>[g|m|k] | 指定java heap的Young Generation的初始大小 ([g|m|k]是單位) |
-XX:MaxNewSize=<n>[g|m|k] | 指定java heap的Young Generation的最大值 ([g|m|k]是單位) |
-Xmn<n>[g|m|k] | 指定java heap的Young Generation的初始值與最大值([g|m|k]是單位)
這個指令等於前兩個指令同時使用並且設定一樣的大小 |
-XX:PermSize=<n>[g|m|k] | 指定java Permanent generation的初始大小
([g|m|k]是單位) |
-XX:MaxPermSize=<n>[g|m|k] | 指定java Permanent generation的最大值
([g|m|k]是單位) |
*Old Generation的size可以用 java heap的總量減掉 Young Generation的大小計算出來
*一般在Production的環境,-Xmx 與-Xms 會設成一樣大,因為調整heap size需要先經過一次full gc
*一般在Production的環境,-XX:PermSize與-XX:MaxPermSize會設成一樣大,因為調整Permanent Generation 的容量需要先經過一次full gc
*若沒有給定以上任何的指令,則JVM會自動所需的值 [See Ch3 HotSpot VM Adaptive Tuning]
- 開始時可以先把heap size設大一點,用 -Xms -Xmx 來設定heap size,設定成你認為的application的需求大小,或是也可以不設定,交給jvm自動判斷
- 開始時,先用 throughput gc (-XX:+UseParallelOldGC)
- 打開 gc log
- 你必需有相關的工具讓你可以用產生類似Production環境時的的工作,並且持續運作,直到application運行到進入 steady state。
- 如果遇到任何 OutOfMemoryErrors ,找出他的原因(是Old GC fail還是 Perm GC fail?)
- 針對OutOfMemoryError的space做加大的動作
- 重覆以上動作,直到不會再遇到 OutOfMemoryError
以下為一次GC產生OutOfMemoryError的例子:
2010-11-25T18:51:03.895-0600: [Full GC [PSYoungGen: 279700K->267300K(358400K)] [ParOldGen: 685165K->685165K(685170K)] 964865K->964865K(1043570K)
[PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00, real=0.05 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space |
從以上的數據我們可以判斷出這是在 Old Gen GC時發生OutOfMemoryError,因為事件發生時,PermGen的size才不到他的limitation的一半,而Old Gen的size則接近他的limitation,而且Old Gen的size在gc完之後機乎沒有變化
目標
- tuning oldGen 的 Live Data Size
- tuning PermGen的 Live Data Size
- 計算出 full GC造成的 latency
方法
- 當程式進入 steady state,經過幾次的 full GC,full GC後的 heap size就會是Live Data Size
- 可以多收集幾次的gc的數據
- 若GC不常發生,可以透過人工誘發的方式,例如 jconsole/ jvisualVM
- command line強制gc的方式:
jmap -histo:live <pid>
例子:假設這是app處於steady state後的一次FullGC的結果
[Full GC [PSYoungGen: 273724K->0K(358400K)] [ParOldGen: 426892K->295111K(685170K)] [700616K->295111K(1048570K) [PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00 real=0.05 secs] |
- OldGen的 Live Data Size是 295111K(約295MB)
- PermGen的 Live Data Size是32390K(約32MB)
指令 | 設定 |
-Xms
-Xmx | 3x~4x OldGen的 Live Data Size |
-XX:PermSize -XX:MaxPermSize | 1.2x~1.5x PermGen 的Live Data Size |
-XX:NewSize
-XX:MaxNewSize
-Xmn | 約 1x~1.5x OldGen的LiveDataSize |
* 因為 -Xms, -Xmx會是3x~4x OldGen LiveDataSize,而YoungGen是 1x~1.5x,所以OldGen差不多會是2x~3x OldGen Live Data Size
範例:
[Full GC [PSYoungGen: 273724K->0K(358400K)] [ParOldGen: 426892K->295111K(685170K)] [700616K->295111K(1048570K) [PSPermGen: 32390K->32390K(65536K)], 0.2499342 secs] [Times: user=0.08 sys=0.00 real=0.05 secs] |
- OldGen的Live Data Size是 295111K(295MB)
- PermGen的Live Data Size是 32390K(32MB)
- 可算出 -Xms -Xmx的建議範圍是 885MB~1180MB,例子中目前是1048MB,在範圍內
- 算出PermGen的建議值是 38MB~48MB,例子中是64MB,雖然超過建議值,但比起1048MB並不算佔用很大,要不要調整都行
- YoungGen的建議大小是 295MB ~ 442MB,目前的設定是 358400K(358MB)
- 從以上計算的結果,可以得到建議的command line:
-Xms1180m -Xmx1180m -Xmn295m -XX:PermSize=48m -XX:MaxPermSize=48m |
- heap size並不等於application真正的記憶體用量,例如JNI使用的額外記憶體,或是多thread會需要更多的thread stack…等等
- 可以使用 OS提供的工具程式來了解實際的記憶體用量
ex: Solaris 的 "
prstat", Linux 的 "
top", windows的 "
TaskManager"
- 假設調整完發現機器的實體記憶體無法滿足需求時,可能會需要降低一些其他需求,或是修改程式(可能的做法是做 heap profiling,並且修改程式,降低 Live Data Size)
- 這邊只是初始的設定,後續的調整也可能會修改到目前的結果
Step 5: Tune Latency/Responsiveness
目標: tuning 由 GC 所造成的 latency
首先先根據上一個步驟的設定結果,做一些GC方面的測量:
- minor GC的所需時間
- minor GC的頻率
- full GC所需時間
- full GC的頻率
*1跟2是調整YoungGen的依據
*3跟4可以用來幫間調整OldGen ,也可能會因此換別種GC方式
- 主要是根據minor GC的所需時間與頻率來調整
- 降低YoungGen的大小可以有效減少GC所需的時間,但是會使GC的頻率上升;反之則會增加GC時間,但是降低頻率。
- 根據所訂的目標latency與發生頻率,重複測試、調整,直到符合需求。
- 注意,過程中只調整YoungGen的size,old gen的size必需固定。
範例:
假設目標 minor GC所需時間少於 50ms,發生頻率低於 次/5秒
初始時 -Xmn=2048m,-Xms=6144m
範例1: 若minor GC時間是60ms,可考慮減少10% -Xmn,試試 -Xmn=1843m -Xms=5939m
範例2: 若minor GC頻率是 次/2秒,則考慮增加兩倍的-Xmn,用 -Xmn=4096m -Xms=8192m
*以上兩範例都只調整YoungGen的大小,沒有改變OldGen的大小
*反複調整以得到最後的結果
*注意,調整時YoungGen的size請不要低於 -Xms的 10%,否則gc的頻率會太高
*調整時要注意實體記憶體大小,若heap 調太大可能會導至程式運行時會使用到OS的swap,那會使效能大幅下降
*假設最終無法達到需求,那就必需考慮修改程式、改變佈屬的JVM的數量,或是降低需求
- 主要根據FullGC的所需時間與頻率來調整
- 若真的沒有FullGC發生,那就用工具來強制執行FullGC
- 根據minor GC的統計數據,可以算出每次minor GC平均增加的OldGen heap size,搭配minor GC的頻率與OldGen的size,我們可以預測FullGC的頻率
- 調整方式類似Young Generation(增加size降低頻率,增加花費時間)
- 注意此時只調整OldGen的size
若在這個階段,經過調整之後可以達成需求,那就可以跳往下個步驟。
若無法達成需求,那可能可以考慮改成使用 Concurrent GC (CMS)。
接下來會介紹CMS GC的tuning
command line option:
-XX:+UseConcMarkSweepGC
- promote Old Generation較慢,所以如果minor GC需要promote OldGen,會需要較長時間
- throughput下降,因為在程式執行中會concurrent對OldGen做gc,但因此fullGC頻率會較低
- 只有在 OldGen不足時,才會做compacting,compacting GC會造成stop-the-world
- compacting 速度很慢
- OldGen的size會調高
20%~30%
- tuning 目標:避免 stop-the-world compacting GC
tuning的重點
- promote rate
- concurrent OldGen gc rate
- fragmentation of OldGen
Promote Rate主要是Tuning CMS的Young Generation 的size
一般的Promote流程:
- 新allocate的Object會放進eden
- 若eden滿了會發生 minor GC
- minor GC會清空eden與一個survivor space,將生下來的object放在另一個survivor space
- 若survivor space不足以放下minor GC的結果(稱為Overflow),則會發生promote OldGen
Promote Rate的調整的目標在於,調整Survivor space的大小,讓Object只有在真的夠老了才被Promote OldGen。
Command Line:
指令 | 解說 |
-XX:SurvivorRatio=<ratio> | - survivor size == -Xmn<value>/(-XX:SurvivorRatio=<ratio> + 2)
- 所以ratio越大,survivor space越小
- 在其他條件不變的情況下,ratio設定越小,survivor空間越大較不易發生Overflow,但是相對來說,eden越小所以minor GC的機會增加,反而會造成object Age增加的速度變快
- 範例: -Xmn512m -XX:SurvivorRatio=2
則 eden = 256m, Survivor is 128+128 |
-XX:TargetSurvivorRatio=<percent> | - 預設 50
- 在allocate survivor space時的比例,若50%的話,那survivor就只會有一半的size
- 範例: -Xmn512m -XX:SurvivorRatio=2
則 eden = 256m, Survivor is 128+128
但因預設 -XX:TargetSurvivorRatio=50,所以實際運行時survivor space只會有 64mb |
-XX:MaxTenuringThreshold=<n> | - n= 0-15
- 用來設定 survivor object的max age
- ParNew gc會自己計算 Tenuring Threshold,不管ParNew算出來的Tenuring Threshold多少,object的age都不會超這邊設定的max值
- 當 survivor object的age大於 Tenuring Threshold,就會被promote到 OldGen
- 設成0的話,每次minor GC都會發生Promote
- Tenuring Threshold太大的話,object會存活太久,Overflow的機率增加 |
-XX:+PrintTenuringDistribution | 設定輸出 survivor space的資訊 |
調整範例:
A.
Desired survivor size 8388608 bytes, new threshold 1 (max 15)
- age 1: 16690480 bytes, 16690480 total |
- ParNew計算的Threshold是1,max設15,Survivor的size是 8MB
- 若 -XX:TargetSurvivorRatio=50,可推出原設定是 -Xmn512 -XX:SurvivorRatio=30 ((512/32)*0.5 = 8)
- 此次GC的結果,age 1的 object有 16mb,超出了Survivor的大小,因此此次GC會有overflow產生,會有8MB的object被promote到OldGen
- 則若要調整讓此次GC不做Promote的話,survivor space的大小就要有 32m(32*0.5=16),那新的設定會是 -Xmn544 -XX:SurvivorRatio=15 (注意,-Xmn變大是因為要保持eden不變)
- 若heap size不能變的話,那eden就需要變小,那設定會是 Xmn512m -XX:SurvivorRatio=14
B
Desired survivor size 16777216 bytes, new threshold 15 (max 15)
- age 1: 6115072 bytes, 6115072 total
- age 2: 286672 bytes, 6401744 total
- age 3: 115704 bytes, 6517448 total
- age 4: 95932 bytes, 6613380 total
- age 5: 89465 bytes, 6702845 total
- age 6: 88322 bytes, 6791167 total
- age 7: 88201 bytes, 6879368 total
- age 8: 88176 bytes, 6967544 total |
- 觀察後可發現 age > 3之後沒什麼變化,可以考慮將threshold max設成3或4,提早將object promote到OldGen,減少不必要的Object搬移
調整注意事項
- Promote rate主要是調整YoungGen,但還是要注意,若YoungGen調太大,則minor GC的所需時間可能會上升,若因此造成latency太長,那就必需做出取捨,可能要為了latency調低YoungGen的大小,雖然Promote Rate會因此上升
- 若無法得到滿意的結果,那就必需修改程式或是降低需求或佈屬更多的JVM
- tuning concurrent OldGen gc rate
- 主要是要tuning CMS collction Cycle
- 讓CMS的OldGen space能維持足夠的空間,防止因為OldGen空間不足而造成的Stop-the-world compacting GC。
- 若collection cycle太慢,則來不及清理OldGen,會造成Stop-the-world compacting GC;反之,若太快,則會造成不必要的效能損失,降低throughput,但通常這總比Stop-the-world好
先產生一次 stop-the-world
這次的tuning從一次stop-the-world gc的發生開始,log如下:
174.445: [GC 174.446: [ParNew: 66408K->66408K(66416K), 0.0000618 secs]174.446: [CMS (concurrent mode failure): 161928K->162118K(175104K), 4.0975124 secs] 228336K->162118K(241520K) |
* 注意 concurrent mode failure,CMS GC若出現此訊息代表這是個 stop-the-world GC
調整 CMSInitiatingOccupancyFraction
-XX:CMSInitiatingOccupancyFraction=<percent> | - 設定CMS collection cycle的門檻
- 代表當OldGen超過該百分比時,會發生 CMS cycle
- 可以先從 Live Data Size的 1.5倍開始調整 |
-XX:+UseCMSInitiatingOccupancyOnly | - 若沒加上此command的話,CMSInitiatingOccupancyFraction 只會發生一次 |
* 注意,計算CMSInitiatingOccupancyFraction的比例時需要注意Live Data Size,否則可能會造成一直不斷發生gc
* 若調整時發現算完 CMSInitiatingOccupancyFraction 的空間小於Live Data Size,代表需要增加OldGen的容量
*反複調整 CMSInitiatingOccupancyFraction,直到可以符合需求
範例:
原設定: -Xmx1536m -Xms1536 -Xmn512m
OldGen size == 1024,推算 Live Data Size約 350m
CMSInitiatingOccupancyFraction先從1.5倍Live Data Size開始,所以是350*1.5=525m, 525m/1024m = 51%
結果:
-Xmx1536m -Xms1536m -Xmn512m -XX:CMSInitiatingOccupancyFraction=51 -XX:+UseCMSInitiatingOccupancyOnly
範例A:CMS cycle太晚
[ParNew 742993K->648506K(773376K), 0.1688876 secs]
[ParNew 753466K->659042K(773376K), 0.1695921 secs]
[CMS-initial-mark 661142K(773376K), 0.0861029 secs]
[Full GC 645986K->234335K(655360K), 8.9112629 secs]
[ParNew 339295K->247490K(773376K), 0.0230993 secs]
[ParNew 352450K->259959K(773376K), 0.1933945 secs] |
* CMS-initial-mark完馬上發生Full GC通常代表 CMS Cycle太晚發生,導至OldGen容量不足
範例B:CMS cycle太早
[ParNew 390868K->296358K(773376K), 0.1882258 secs]
[CMS-initial-mark 298458K(773376K), 0.0847541 secs]
[ParNew 401318K->306863K(773376K), 0.1933159 secs]
[CMS-concurrent-mark: 0.787/0.981 secs]
[CMS-concurrent-preclean: 0.149/0.152 secs]
[CMS-concurrent-abortable-preclean: 0.105/0.183 secs]
[CMS-remark 374049K(773376K), 0.0353394 secs]
[ParNew 407285K->312829K(773376K), 0.1969370 secs]
[ParNew 405554K->311100K(773376K), 0.1922082 secs]
[ParNew 404913K->310361K(773376K), 0.1909849 secs]
[ParNew 406005K->311878K(773376K), 0.2012884 secs]
[CMS-concurrent-sweep: 2.179/2.963 secs]
[CMS-concurrent-reset: 0.010/0.010 secs] |
* 可以發現CMS-initial-mark跟 CMS-concurrent-reset中間的ParNew幾乎沒變化,這代表CMS cycle太常發生
* 這個範例中,從 CMS-initial-mark 298458K(773376K) 可知道 CMSInitiatingOccupancyFraction 約是 35%~40% (因為 298458/773376 = 38.5),我們可以將 CMSInitiatingOccupancyFraction設高點(ex: 50%),來減慢CMS Cycle的週期
範例C: 一個正常運作的CMS GC回收了大量object而不必stop-the-world
[ParNew 640710K->546360K(773376K), 0.1839508 secs]
[CMS-initial-mark 548460K(773376K), 0.0883685 secs]
[ParNew 651320K->556690K(773376K), 0.2052309 secs]
[CMS-concurrent-mark: 0.832/1.038 secs]
[CMS-concurrent-preclean: 0.146/0.151 secs]
[CMS-concurrent-abortable-preclean: 0.181/0.181 secs]
[CMS-remark 623877K(773376K), 0.0328863 secs]
[ParNew 655656K->561336K(773376K), 0.2088224 secs]
[ParNew 648882K->554390K(773376K), 0.2053158 secs]
[ParNew 489586K->395012K(773376K), 0.2050494 secs]
[ParNew 463096K->368901K(773376K), 0.2137257 secs]
[CMS-concurrent-sweep: 4.873/6.745 secs]
[CMS-concurrent-reset: 0.010/0.010 secs]
[ParNew 445124K->350518K(773376K), 0.1800791 secs] |
* 觀察 CMS-initial-mark 與 CMS-concurrent-reset 之間的 ParNew可發現顯著的下降
- 其他:Explicit Garbage Collections
Explicit GC指的是用RMI、或是System.gc()、Command Line 一類的方式直接觸發 java application發動GC。這種情況會產如如下的log:
2010-12-16T23:04:39.452-0600: [Full GC (System) [CMS: 418061K->428608K(16384K), 0.2539726 secs] |
* 注意那個 (System),即代表此次GC是Explicit GC的
當發生此類事件事,可能需要思考他發生的時機是不是正確的,有沒有必要禁止這類的行為…
與Explicit Garbage Collections有關的Command:
-XX:+ExplicitGCInvokesConcurrent | - 需要JDK6+
- 讓Explicit GC也能Concurrent進行 |
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses | - 需要JDK6U4+
- 讓Explicit GC也能Concurrent進行
- 較建議使用 |
-XX:+DisableExplicitGC | - 禁止 Explicit GC |
- Concurrent Permanent Generation Garbage Collection
FullGC也可能是因為 PermGen的空間不足
範例:
[Full GC [CMS: 95401K->287072K(1048576K), 0.5317934 secs] 482111K->287072K(5190464K), [CMS Perm : 65534K->58281K(65536K)], 0.5319635 secs] |
*此次Full GC 發生時 PermGen的size接近他的上限,由此可知此次FullGC是由PermGen空間不足而觸發的
一般情況,CMS不會對PermGen做GC,可以考慮以下Command:
-XX:+CMSClassUnloadingEnabled | 加上此選項讓CMS對PermGen做GC |
-XX:+CMSPermGenSweepingEnabled | 在J6U3以前,要額外加上此參數 |
-XX:CMSInitiatingPermOccupancyFraction=<percent> | 類似CMSInitiatingOccupancyFraction,當PermGen的使用達到此百分比時,就會對PermGen做GC |
-XX:+UseCMSInitiatingOccupancyOnly | 類似UseCMSInitiatingOccupancyOnly,若沒加上此設定的話,CMSInitiatingPermOccupancyFraction只會作用一次 |
CMS會造成 stop the world的情況只有兩個Phase:
- initial mark phase : 通常不會很久
- remark phase : 通常會花較多時間,多執行緒
-XX:ParallelGCThreads=<n> | - 可用來指定 remark phase的thread數
- 若Runtime.availableProcessors() 小於8,則預設值等於 Runtime.availableProcessors()
- 若 Runtime.availableProcessors() 大於8,則預設值等於 (5/8)*Runtime.availableProcessors()
- 若系統上有其他的app的話,建議將此數值調低於預設值,否則此application在GC時,會造成其他的application變慢 |
-XX:+CMSScavengeBeforeRemark | 強制在 CMS remark之前做一次 minor GC,可減少 CMS remark的工作量,降低所需時間 |
-XX:+ParallelRefProcEnabled | - 使用多執行緒來查找 finalizable object
- 這邊的多執行緒只針對尋找,並不用來執行 finalizer
- 適合用在可能有大量 reference與 finalizable object的情況
- 可以任何GC搭配使用,而不止能用在CMS |
如果最終可以得到一個結果是可以滿足latency的需求,那就可以進入下個階段
如果沒辦法滿足需求,那可能會需要降低需求、修改程式,或是將程式佈屬到更多的JVM上