JVM性能調優涉及到方方面面的取捨,每每是牽一髮而動全身,須要全盤考慮各方面的影響。但也有一些基礎的理論和原則,理解這些理論並遵循這些原則會讓你的性能調優任務將會更加輕鬆。爲了更好的理解本篇所介紹的內容。你須要已經瞭解和遵循如下內容:java
一、已瞭解jvm 垃圾收集器性能優化
二、已瞭解jvm 性能監控經常使用工具架構
三、可以讀懂gc日誌併發
四、確信不爲了調優而調優,jvm調優不能解決一切性能問題jvm
若是對這些不瞭解不建議讀本篇文章。函數
本篇文章基於jvm性能調優,結合jvm的各項參數對應用程序調優,主要內容有如下幾個方面:工具
一、jvm調優的通常流程性能
二、jvm調優所要關注的幾個性能指標測試
三、jvm調優須要掌握的一些原則優化
四、調優策略&示例
1、性能調優的層次
爲了提高系統性能,咱們須要對系統的各個角度和層次來進行優化,如下是須要優化的幾個層次。
從上面咱們能夠看到,除了jvm調優之外,還有其餘幾個層面須要來處理,因此針對系統的調優不是隻有jvm調優一項,而是須要針對系統來總體調優,才能提高系統的性能。本篇只針對jvm調優來說解,其餘幾個方面,後續再介紹。
在進行jvm調優以前,咱們假設項目的架構調優和代碼調優已經進行過或者是針對當前項目是最優的。這兩個是jvm調優的基礎,而且架構調優是對系統影響最大的 ,咱們不能期望一個系統架構有缺陷或者代碼層次優化沒有窮盡的應用,經過jvm調優令其達到一個質的飛躍,這是不可能的。
另外,在調優以前,必須得有明確的性能優化目標, 而後找到其性能瓶頸。以後針對瓶頸的優化,還須要對應用進行壓力和基準測試,經過各類監控和統計工具,確認調優後的應用是否已經達到相關目標。
2、jvm調優流程
調優的最終目的都是爲了令應用程序使用最小的硬件消耗來承載更大的吞吐。jvm的調優也不例外,jvm調優主要是針對垃圾收集器的收集性能優化,令運行在虛擬機上的應用可以使用更少的內存以及延遲獲取更大的吞吐量。固然這裏的最少是最優的選擇,而不是越少越好。
一、性能定義
要查找和評估器性能瓶頸,首先要知道性能定義,對於jvm調優來講,咱們須要知道如下三個定義屬性,依做爲評估基礎:
- 吞吐量:重要指標之一,是指不考慮垃圾收集引發的停頓時間或內存消耗,垃圾收集器能支撐應用達到的最高性能指標。
- 延遲:其度量標準是縮短因爲垃圾啊收集引發的停頓時間或者徹底消除因垃圾收集所引發的停頓,避免應用運行時發生抖動。
- 內存佔用:垃圾收集器流暢運行所須要 的內存數量。
這三個屬性中,其中一個任何一個屬性性能的提升,幾乎都是以另一個或者兩個屬性性能的損失做代價,不可兼得,具體某一個屬性或者兩個屬性的性能對應用來講比較重要,要基於應用的業務需求來肯定。
二、性能調優原則
在調優過程當中,咱們應該謹記如下3個原則,以便幫助咱們更輕鬆的完成垃圾收集的調優,從而達到應用程序的性能要求。
1. MinorGC回收原則: 每次minor GC 都要儘量多的收集垃圾對象。以減小應用程序發生Full GC的頻率。
2. GC內存最大化原則:處理吞吐量和延遲問題時候,垃圾處理器能使用的內存越大,垃圾收集的效果越好,應用程序也會愈來愈流暢。
3. GC調優3選2原則: 在性能屬性裏面,吞吐量、延遲、內存佔用,咱們只能選擇其中兩個進行調優,不可三者兼得。
三、性能調優流程
以上就是對應用程序進行jvm調優的基本流程,咱們能夠看到,jvm調優是根據性能測試結果不斷優化配置而屢次迭代的過程。在達到每個系統需求指標以前,以前的每一個步驟都有可能經歷屢次迭代。有時候爲了達到某一方面的指標,有可能須要對以前的參數進行屢次調整,進而須要把以前的全部步驟從新測試一遍。
另外調優通常是從知足程序的內存使用需求開始的,以後是時間延遲的要求,最後纔是吞吐量的要求,要基於這個步驟來不斷優化,每個步驟都是進行下一步的基礎,不可逆行之。如下咱們針對每一個步驟進行詳細的示例講解。
在JVM的運行模式方面,咱們直接選擇server模式,這也是jdk1.6之後官方推薦的模式。
在垃圾收集器方面,咱們直接採用了jdk1.6-1.8 中默認的parallel收集器(新生代採用parallelGC,老生代採用parallelOldGC)。
3、肯定內存佔用
在肯定內存佔用以前,咱們須要知道兩個知識點:
- 應用程序的運行階段
- jvm內存分配
一、運行階段
應用程序的運行階段,我能夠劃分爲如下三個階段:
一、初始化階段 : jvm加載應用程序,初始化應用程序的主要模塊和數據。
二、穩定階段:應用在此時運行了大多數時間,經歷過壓力測試的以後,各項性能參數呈穩定狀態。核心函數被執行,已經被jit編譯預熱過。
三、總結階段:最後的總結階段,進行一些基準測試,生成響應的策報告。這個階段咱們能夠不關注。
肯定內存佔用以及活躍數據的大小,咱們應該是在程序的穩定階段來進行肯定,而不是在項目起初階段來進行肯定,如何肯定,咱們先看如下jvm的內存分配。
二、jvm內存分配&參數
jvm堆中主要的空間,就是以上新生代、老生代、永久代組成,整個堆大小=新生代大小 + 老生代大小 + 永久代大小。 具體的對象提高方式,這裏再也不過多介紹了,咱們看下一些jvm命令參數,對堆大小的指定。若是不採用如下參數進行指定的話,虛擬機會自動選擇合適的值,同時也會基於系統的開銷自動調整。
分代
參數
描述
堆大小
-Xms
初始堆大小,默認爲物理內存的1/64(<1GB)
-Xmx
最大堆大小,默認(MaxHeapFreeRatio參數能夠調整)空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制
新生代
-XX:NewSize
新生代空間大小初始值
-XX:MaxNewSize
新生代空間大小最大值
-Xmn
新生代空間大小,此處的大小是(eden+2 survivor space)
永久代
-XX:PermSize
永久代空間的初始值&最小值
-XX:MaxPermSize
永久代空間的最大值
老年代
老年代的空間大小會根據新生代的大小隱式設定
初始值=-Xmx減去-XX:NewSize的值
最小值=-Xmx值減去-XX:MaxNewSize的值
在設置的時候,若是關注性能開銷的話,應儘可能把永久代的初始值與最大值設置爲同一值,由於永久代的大小調整須要進行FullGC 才能實現。
三、計算活躍數據大小
計算活躍數據大小應該遵循如下流程:
如前所述,活躍數據應該是基於應用程序穩定階段時,觀察長期存活與對象在java堆中佔用的空間大小。
計算活躍數據時應該確保如下條件發生:
1.測試時,啓動參數採用jvm默認參數,不人爲設置。
2.確保Full GC 發生時,應用程序正處於穩定階段。
採用jvm默認參數啓動,是爲了觀察應用程序在穩定階段的所須要的內存使用。
如何纔算穩定階段?
必定得須要產生足夠的壓力,找到應用程序和生產環境高峯符合狀態相似的負荷,在此以後達到峯值以後,保持一個穩定的狀態,纔算是一個穩定階段。因此要達到穩定階段,壓力測試是必不可少的,具體如何如何對應用壓力測試,本篇不過多說明,後期會有專門介紹的篇幅。
在肯定了應用出於穩定階段的時候,要注意觀察應用的GC日誌,特別是Full GC 日誌。
GC日誌指令: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>
GC日誌是收集調優所需信息的最好途徑,即使是在生產環境,也能夠開啓GC日誌來定位問題,開啓GC日誌對性能的影響極小,卻能夠提供豐富數據。
必須得有FullGC 日誌,若是沒有的話,能夠採用監控工具強制調用一次,或者採用如下命令,亦能夠觸發
jmap -histo:live pid
在穩定階段觸發了FullGC咱們通常會拿到以下信息:
從以上gc日誌中,咱們大概能夠分析到,在發生fullGC之時,整個應用的堆佔用以及GC時間,固然了,爲了更加精確,應該多收集幾回,獲取一個平均值。或者是採用耗時最長的一次FullGC來進行估算。
在上圖中,fullGC以後,老年代空間佔用在93168kb(約93MB),咱們以此定爲老年代空間的活躍數據。
其餘堆空間的分配,基於如下規則來進行。
空間
命令參數
建議擴大倍數
java heap
-Xms和-Xmx
3-4倍FullGC後的老年代空間佔用
永久代
-XX:PermSize
-XX:MaxPermSize
1.2-1.5倍FullGc後的永久帶空間佔用
新生代
-Xmn
1-1.5倍FullGC以後的老年代空間佔用
老年代
2-3倍FullGC後的老年代空間佔用
基於以上規則和上圖中的FullGC信息,咱們如今能夠規劃的該應用堆空間爲:
java 堆空間: 373Mb (=老年代空間93168kb*4)
新生代空間:140Mb(=老年代空間93168kb*1.5)
永久代空間:5Mb(=永久代空間3135kb*1.5)
老年代空間: 233Mb=堆空間-新生代看空間=373Mb-140Mb
對應的應用啓動參數應該爲:
java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m
4、延遲調優
在肯定了應用程序的活躍數據大小以後,咱們須要再進行延遲性調優,由於對於此時堆內存大小,延遲性需求沒法達到應用的須要,須要基於應用的狀況來進行調試。
在這一步進行期間,咱們可能會再次優化堆大小的配置,評估GC的持續時間和頻率、以及是否須要切換到不一樣的垃圾收集器上。
一、系統延遲需求
在調優以前,咱們須要知道系統的延遲需求是那些,以及對應的延遲可調優指標是那些。
- 應用程序可接受的平均停滯時間: 此時間與測量的Minor GC持續時間進行比較。
- 可接受的Minor GC頻率:Minor GC的頻率與可容忍的值進行比較。
- 可接受的最大停頓時間: 最大停頓時間與最差狀況下FullGC的持續時間進行比較。
- 可接受的最大停頓發生的頻率:基本就是FullGC的頻率。
以上中,平均停滯時間和最大停頓時間,對用戶體驗最爲重要,能夠多關注。
基於以上的要求,咱們須要統計如下數據:
- MinorGC的持續時間;
- 統計MinorGC的次數;
- FullGC的最差持續時間;
- 最差狀況下,FullGC的頻率;
二、優化新生代的大小
好比如上的gc日誌中,咱們能夠看到Minor GC的平均持續時間=0.069秒,MinorGC 的頻率爲0.389秒一次。
若是,咱們系統的設置的平均停滯時間爲50ms,當前的69ms明顯是太長了,就須要調整。
咱們知道新生代空間越大,Minor GC的GC時間越長,頻率越低。
若是想減小其持續時長,就須要減小其空間大小。
若是想減少其頻率,就須要加大其空間大小。
爲了下降改變新生代的大小對其餘區域的最小影響。在改變新生代空間大小的時候,儘可能保持老年代空間的大小。
好比這次減小了新生代空間10%的大小,應該保持老年代和持代的大小不變化,第一步調優後的參數以下變化:
java -Xms359m -Xmx359m -Xmn126m -XX:PermSize=5m -XX:MaxPermSize=5m
新生代的大小有140m變爲126,堆大小順應變化,此時老年代是沒有變化的。
三、優化老年代的大小
同上一步同樣,在優化以前,也須要採集gc日誌的數據。這次咱們關注的是FullGC的持續時間和頻率。
上圖中,咱們能夠看到
FullGC 平均頻率 =5.8s
FullGC 平均持續時間=0.14s
(以上爲了測試,真實項目的fullGC 沒有這麼快)
若是沒有FullGC的日誌,有辦法能夠評估麼?
咱們能夠經過對象提高率進行計算。
對象提高率
好比上述中啓動參數中,咱們的老年代大小=233Mb。
那麼須要多久才能填滿老年代中這233Mb的空閒空間取決於新生代到老年代的提高率。
每次提高老年代佔用量=每次MinorGC 以後 java堆佔用狀況 減去 MinorGC後新生代的空間佔用
對象提高率=平均值(每次提高老年代佔用量) 除以 老年代空間
有了對象提高率,咱們就能夠算出填充滿老年代空間須要多少次minorGC,大概一次fullGC的時間就能夠計算出來了。
好比:
上圖中:
第一次minor GC 以後,老年代空間:13740kb - 13732kb =8kb
第二次minor GC 以後,老年代空間:22394kb - 17905kb =4489kb
第三次minor GC 以後,老年代空間:34739kb - 17917kb =16822kb
第四次minor GC 以後,老年代空間:48143kb - 17913kb =30230kb
第五次minor GC 以後,老年代空間:62112kb - 17917kb =44195kb
老年代每次minorGC提高率
4481kb 第二次和第一次minorGC之間
12333kb 第3次和第2次minorGC之間
13408kb 第4次和第3次minorGC之間
13965kb 第5次和第4次minorGC之間
咱們能夠測算出:
每次minorGC 的平均提高爲12211kb,約爲12Mb
上圖中,平均minorGC的頻率爲 213ms/次
提高率=12211kb/213ms=57kb/ms
老年代空間233Mb ,佔滿大概須要233*1024/57=4185ms 約爲4.185s。
FullGC的預期最差頻率時長能夠經過以上兩種方式估算出來,能夠調整老年代的大小來調整FullGC的頻率,固然了,若是FullGC持續時間過長,沒法達到應用程序的最差延遲要求,就須要切換垃圾處理器了。具體如何切換,下篇再講,好比切換爲CMS,針對CMS的調優方式又有會細微的差異。
5、吞吐量調優
通過上述漫長 調優過程,最終來到了調優的最後一步,這一步對上述的結果進行吞吐量測試,並進行微調。
吞吐量調優主要是基於應用程序的吞吐量要求而來的,應用程序應該有一個綜合的吞吐指標,這個指標基於真個應用的需求和測試而衍生出來的。當有應用程序的吞吐量達到或者超過預期的吞吐目標,整個調優過程就能夠圓滿結束了。
若是出現調優後依然沒法達到應用程序的吞吐目標,須要從新回顧吞吐要求,評估當前吞吐量和目標差距是否巨大,若是在20%左右,能夠修改參數,加大內存,再次從頭調試,若是巨大就須要從整個應用層面來考慮,設計以及目標是否一致了,從新評估吞吐目標。
對於垃圾收集器來講,提高吞吐量的性能調優的目標就是就是儘量避免或者不多發生FullGC 或者Stop-The-World壓縮式垃圾收集(CMS),由於這兩種方式都會形成應用程序吞吐下降。儘可能在MinorGC 階段回收更多的對象,避免對象提高過快到老年代。
6、最後
據Plumbr公司對特定垃圾收集器使用狀況進行了一次調查研究,研究數據使用了84936個案例。在明確指定垃圾收集器的13%的案例中,併發收集器(CMS)使用次數最多;但大多數案例沒有選擇最佳垃圾收集器。這個比例佔用在87%左右。
JVM調優是一個系統而又複雜的工做,目前jvm下的自動調整已經作的比較優秀,基本的一些初始參數均可以保證通常的應用跑的比較穩定了,對部分團隊來講,程序性能可能優先級不高,默認垃圾收集器已經夠用了。調優要基於本身的狀況而來。