其實說到對JVM進行性能調優早已經是一個老生常談的話題,若是你所在的技術團隊還暫時達不到淘寶團隊那樣的高度,沒法知足在OpenJDK的基礎之上根據自身業務進行鍼對性的二次開發和定製調優,那麼對於你來講,惟一的選擇就是儘量的熟悉JVM的內存佈局,以及熟練掌握與GC相關的那些選項配置,不然JVM的基礎性能調優不是癡人說夢?算法
相信對JVM有所瞭解的開發人員,對於調優過程當中牽扯的吞吐性、低延遲/高響應應該不會感受到陌生。既然生產環境中是大規模的分佈式Java平臺,JVM吃的內存必然不會太少。不知你們是否還曾記得,64位的JVM可以順利訪問大內存,其最主要的緣由是由於其採用了64位的指針架構,這同時也是尋址訪問大內存的關鍵要素。而與之相反的32位的JVM的內存卻被限定在了2-3GB上限(與操做平臺密切相關,Linux平臺,Windows則爲1.5G上限)。 大規模的分佈式Java平臺除了JVM吃的內存特別大外(筆者以前的項目單點持有內存爲30GB),爲了增長每個節點的可用性,都是採用多JVM集羣的部署模式,這樣一來一旦發生單點故障的時候,不會致使整個服務不可用,從而也可以下降單點負載,提高總體程序的執行性能,更好的知足一些特定的高併發場景。 話說生產部署在服務器上的JVM大都是主動或者缺省選擇server模式在奔跑,而且在Java7版本以後,JVM缺省開啓了分層編譯(Tiered Compilation)策略,由C1和C2編譯器共同來執行本地代碼的編譯任務,C1編譯器會對字節碼進行簡單和可靠的優化,以達到更快的編譯速度,而C2編譯器則會啓動一些耗時更長的優化,以獲取更好的本地代碼編譯質量。 那麼對JVM進行性能調優的真正目的是什麼呢?簡單來講就是爲了知足程序的高吞吐量、低延遲/高響應性等需求。可是筆者不得不提醒你們,**調優是一個按部就班的過程,必然須要經歷屢次迭代,最終才能換取一個較好的折中方案。**筆者在《Java虛擬機精講》中曾經說起過,垃圾收集器中吞吐量和低延遲這兩個目標實際上是存在相互競爭的矛盾,由於若是選擇以吞吐量優先,那麼下降內存回收的執行頻率則是必然的,但這將會致使GC須要更長的暫停時間來執行內存回收。相反若是是選擇以低延遲優先,那麼爲了下降每次執行內存回收時的暫停時間,只可以頻繁地執行內存回收,但這又引發了新生代內存的縮減和致使程序吞吐量的降低。舉個例子,在60s的JVM總運行時間裏,每次GC的執行頻率是20s/次,那麼60s內一共會執行3次內存回收,按照每次GC耗時100ms來計算,最終一共會有300ms(即60/20*100
)被用於執行內存回收。可是若是咱們將選項「-XX:MaxGCPauseMillis」的值調小後,新生代的內存空間也會自動調整,相信你們都知道,內存空間越小就越容易被耗盡,那麼GC的執行頻率就會更頻繁。以前在60s的JVM總運行時間裏,最終會有300ms被用於執行內存回收,而現在GC的執行頻率倒是10s/次,60s內將會執行6次內存回收,按照每次GC耗時80ms來計算,雖然看上去暫停時間更短了,但最終一共會有480ms(即60/10*80
)被用於執行內存回收,很明顯程序的吞吐量降低了。所以,在JVM調優這個領域,沒有任何一種調優方案是適用於全部應用場景的,同時,切勿極端纔可以達到JVM性能調優的真正目的和意義。服務器
簡而言之,總而言之,對JVM進行性能調優時,有2個基本原則你們須要進行理解。首先是儘量的讓GC發生在新生代中,也就是儘量的多執行Minor GC,由於咱們都知道Full GC的執行頻率儘管不會有Minor GC那麼頻繁,可是對程序響應性的影響是很是大的(筆者以前的項目Full GC詭異般的執行了50s,顯然超出了對響應延遲的容忍度)。那麼多讓Minor GC執行,顯然能夠減小觸發Full GC的頻率。架構
其次,GC所持有的可用內存越大(Java Heap所佔有的堆空間越大),GC的執行效率越好。這是由於內存越大,達到回收閾值就越不容易,那麼明顯能夠提高程序的吞吐量和響應性。固然這並非說越大越好,若是一個項目JVM撐死只須要1-2G的運行內存,人傻錢多分配120G的內存量,或許程序在穩定狀況下運行到硬件故障也不會發生一次Full GC。 既然內存並非越大越好,總有一個閾值。這就牽扯到生產環境中,開發人員究竟應該如何對Heap分配初始大小?其實這很簡單,一個經歷過嚴謹測試的項目,必然會在測試環境中測試N個週期纔會移交至生產環境中進行部署,那麼在測試環境中,咱們能夠根據屢次迭代後觀察Full GC的數據信息來估算生產環境中究竟應該給咱們的項目初始多大的內存空間。好比通過屢次迭代後,Full GC產生的數據信息中,若是老年代中的活躍數據佔用內存大小爲100m,那麼按照通用的計算法則,能夠按照約3-4倍的佔用倍數來恆定生產環境中應該分配的堆大小(即-Xms和-Xmx),新生代和老年代的比例官方建議按照整個堆的3/8來進行分配,也就是說選項-Xmn能夠佔用整個堆內存空間的3/8,這是一種很是簡單和通用的計算和分配方式。而永久代則能夠按照Full GC後產生的數據信息,根據永久代活躍數據佔用內存大小的1.5倍進行恆定生產環境中應該分配的初始值。 這裏筆者稍微補充一下,在一些高併發場景下,尤爲關注吞吐量和高響應的應用中,應該將-Xms和-Xmx設定爲同一值,以此避免內存動態調整時產生的Full GC操做,永久代-XX:PermSize和-XX:MaxPermSize同理。併發
在HotSpot中,串行回收GC與並行回收GC是2個極端,在現在,更多人更傾向於選擇後者,而且在一些極其注重吞吐量和高響應的應用場景下,並行回收有着串行回收沒法比擬的絕對優點。因爲堆空間中的對象大部分都是一些瞬時對象,所以這類對象的生命週期每每更可能是由新生代進行「控制」,以前也說過,儘量的讓垃圾收集動做發生在新生代中,而不是Full GC。這樣一來,對於新生代的性能調優就主要集中在幾個問題上,首先是測量出Minor GC的執行平率和持續時間是否知足需求,以及-XX:ParallelGCThreads選項的配置。如圖A-1所示:分佈式
若是說Minor GC執行的太頻繁,那麼必然是-Xmn分配得太小,反之Minor GC好久才執行一次,而每次執行的週期較長,則意味着-Xmn分配得過大。那麼究竟應該如何對新生代進行調優呢?簡單來講,咱們須要屢次迭代,從最初將-Xmn的值設置到最低,而後逐步微調,慢慢的你會發現Minor GC的執行頻率在下降,直到最終知足需求便可中止。通過這樣的調試,你會發現程序的吞吐量上來了,可是每次執行Minor GC的週期會變得較長,怎麼辦呢?咱們能夠經過-XX:ParallelGCThreads選項調整GC執行的線程數,讓更多的GC線程執行垃圾收集,提高GC的回收效率。這樣一來,基本能夠知足下降GC的回收平率,提高GC的回收效率。 因爲使用的是並行GC,咱們能夠充分利用多核CPU資源以及線程資源。同微調-Xmn選項同樣,咱們首先能夠將-XX:ParallelGCThreads設置爲物理CPU核心數的1/2,好比你的CPU是6核,那麼-XX:ParallelGCThreads的值就能夠設置爲3(最好不要小於2,不然將會影響並行GC的回收效率),這樣一來,CPU可用資源就會將一半分配給GC線程使用,而剩下的CPU資源則服務於應用線程中。固然若是你的項目並不重視高響應,-XX:ParallelGCThreads的值能夠相對的進行減小,以便於有更多的CPU資源分配給程序中的工做線程。高併發
新生代的調優若是你們都已經掌握,接下來咱們再來看老年代如何進行性能調優。儘管調優原則中筆者說起過,應該讓垃圾收集動做盡量的發生在新生代中,也就是儘量多執行Minor GC,可是這並不表明程序永遠不會執行Full GC,一旦程序觸發Full GC時,所花費的時間每每要大於Minor GC的執行週期,若是Full GC執行的週期過長,對用戶所帶來的直觀感覺是很是不友好的,好比用戶在執行登陸操做,偏偏悲催的遇見JVM正在執行長時間的Full GC,請自行補白。。。 在GC的命令選項中並不存在直接設置來年代內存大小的選項,那麼老年代的內存大小如何設置呢?簡單來講,老年代的內存空間大小間接等於-Xmx的值減去-Xmn的值,好比-Xmx爲120G,-Xmn的值爲45G,那麼剩下的75G就是老年代的內存空間。在此你們須要注意,若是當-Xmn產生變化時,-Xmx也要隨之成比例的發生變化,不然老年代佔用的內存空間將會增大或變小,若是增大,Full GC的執行週期將會變得更長,反之執行頻率將會頻繁。 通常來講,若是<=3G如下的堆內存,建議使用的GC組合是Parallel和Parallel Old,除非真的是需求沒法容忍系統出現長時間的「Stop the World」(目前幾乎沒有任何一款GC不須要暫停工做線程,只是儘量的縮短暫停時間,包括G1)狀況下,才推薦上CMS,不過通常大內存的使用,老年代首推CMS執行垃圾收集,而且CMS也是除G1以外的HotSpot中惟一的一款能夠單獨執行老年代增量回收,而沒必要執行Full GC全量回收的垃圾收集器(Promotion Failed和Concurrent Mode Failed狀況除外)。佈局
之因此要用CMS,是由於CMS天生爲低延遲/高響應而生。由於CMS的執行過程當中,只有初始標記和再次標記會出現暫停,而其它過程CMS的工做線程將會和程序的工做線程同時工做,大大提高了GC的回收效率。那麼使用CMS一樣須要進行優化,其中最主要的就是調整-Xmx的大小和-XX:CMSInitiatingOccupancyFraction選項。如圖A-2所示:性能
-XX:CMSInitiatingOccupancyFraction用於設置老年代中的內存使用率達到多少百分比的時候執行內存回收(低版本的JDK缺省值爲68%,JDK6及以上版本缺省值則爲92%),在JDK6之後續版本中,若是按照缺省配置,當老年代的內存使用率達到92%後才進行垃圾收集,這每每會致使重新生代晉升到老年代中的對象將沒法進行存放,若是-XX:CMSInitiatingOccupancyFraction設置得過低又會致使CMS GC觸發的頻率太快。通常來講,在大內存的堆使用上,筆者將這個值設置在70-80之間算是比較合理的。 儘管CMS是大內存的首選,可是CMS仍然是有一些使人不滿意的地方,好比搶佔CPU資源、內存碎片等問題。不過總而言之,CMS目前在大內存的使用上,仍然是首選。測試