如何合理的規劃一次jvm性能調優?

Zeng-Liping


很多同學覺得JVM調優就是簡單調整幾個JVM內存幾個參數,我認為這個觀點是非常片面的。JVM調優有很多門檻,不只是修改幾個參數那個簡單。

其實我想回答如何進行JVM調優,但是可能和題主的問題稍微有些出入。但是我想通過我對JVM的理解幫助到更多這方面知識欠缺的童鞋。

關注必回!

我認為JVM調優可以從以下幾個方面考慮:

對JVM內存模型有一定認識是能夠對JVM調優的基本門檻,下面我簡單介紹一下jvm內存模型,對這部分知識還有不明確的可以閱讀我jvm詳解的文章。

  • jvm內存可以簡單分為堆,棧和元空間。題主的調優主要針對堆內存,當然也有棧。但是前者居多。
  • jvm的精明之處!垃圾回收機制,jvm根據內存使用情況,根據引用計數法和根目錄遍歷的方式結合age對堆內對象的生命週期進行管理,當然因為一個對象的創建在堆棧中都分配內存空間(引用在棧中,對象本身在堆中),所以垃圾回收機制對堆和棧都效果!
  • 分代垃圾回收機制,通過分代更合理的掌控對象的生命週期。
  • 根據業務需要合理的進行GC以達到最優內存使用率。

分析JVM內存的增長因子,快速定位大塊老賴對象

如果在程序運行期間,內存突然上升,並且在一定時間沒有下降趨勢,那麼說明,有一大波對象逃過了垃圾回收,我們都知道,新生代垃圾回收是頻繁的,但是新生代內存空間有限,內存持續上升,說明大部分對象已經進入老年帶,根據引用計數和不可達算法,快速定位問題,留意近期的哪些改動會產生大量持續被引用對象,或者僅僅是持續產生大量對象,因為gc往往是阻塞的,短時間內gc掉大量對象對應用程序也會造成傷害,倒置線程長時間等待以致假死。

在這個時候猜測往往是效率最高的解決方案!大膽的預測問題並去驗證它。如果猜測短時間內沒有定位問題,那麼通過top命令查找線程一步一步的找吧,也很快的,因為手機碼字,這塊兒就不細說了,有不清楚的私信!

JAVA虛擬機啟動過程監控。

一個項目啟動是JVM會將用到的類文件加載進內存,加載所有配置,加載所有bean對象。在這個過程中內存是不斷增加的,可以通過jvm-

monitor.sh

。想了解JVM虛擬機啟動的可以先看看自己公司項目的啟動日誌,可窺一二。

腳本進行內存監控,具體腳本內容比較冗長,在問答中不便貼出來了,有需要的童鞋直接私信我。

調整JVM堆棧內存分配大小,合理規避業務中內存溢出等問題

理論不多說,直接給出結論:

  • 堆內存中,老年代:新生代比例=2:1;

  • 新生代中,Eden:SurvivorA:SurvivorB=8:1:1;

  • -Xms:初始化堆大小;Xmx:最大堆大小;

  • -XX:NewSize=x,設置年輕代大小;-XX NewRatio=x 年輕代和老年代比例。

JVM調優工具的使用,快速定位內存問題利器

遇到

1、java.lang.OutOfMemoryError: PermGen space

2、java.lang.OutOfMemoryError: Java heap space 兩種異常,通過大膽猜測未能解決問題的,可以通過LInux命令查找到問題代碼塊。常用命令簡單整理如下(需要實戰的可以私信我):

  1. top,通過top命令,快速定位佔用內存較大的java進程。

  2. jmap -histo:live [pid] ,查看當前java進程中活躍對象的數目和佔用內存大小。

  3. jmap -dump:live,format=b,file=xxx.xxx [pid] 導出指定pid的java進程的內存佔用情況,以便通過內存分析工具進行分析,具體的內存分析工具包括HeapSnapShot、HeapViewer、MAT等。門檻都不高,能夠幫助我們快速定位出問題癥結。

  4. java本身也提供好幾款內存監控分析插件,位於jdk的bin目錄下:具體使用方法本人多年前在csdn上有文章詳解,這裡不允許貼鏈接,也就不多說了。

本人從事軟件工作多年,一路走來也不斷積累不斷學習,整理了一些java開發相關的資料,也包含JVM調優,需要的可以在下方留言或者留下郵箱(不是圈粉,所以可以不用關注)。

我是狂客說技術,總想著把自己掌握的東西整理一下分享出來,怎奈時間有限,可能文中也有不當的地方,歡迎大家指出,也歡迎大家點我關注。


狂客說技術


JVM性能調優涉及到方方面面的取捨,往往是牽一髮而動全身,需要全盤考慮各方面的影響。但也有一些基礎的理論和原則,理解這些理論並遵循這些原則會讓你的性能調優任務將會更加輕鬆。為了更好的理解本篇所介紹的內容。你需要已經瞭解和遵循以下內容:

1、已瞭解jvm 垃圾收集器2、已瞭解jvm 性能監控常用工具3、能夠讀懂gc日誌4、確信不為了調優而調優,jvm調優不能解決一切性能問題

如果對這些不瞭解不建議讀本篇文章。

本篇文章基於jvm性能調優,結合jvm的各項參數對應用程序調優,主要內容有以下幾個方面:

1、jvm調優的一般流程2、jvm調優所要關注的幾個性能指標3、jvm調優需要掌握的一些原則4、調優策略&示例

一、性能調優的層次

為了提升系統性能,我們需要對系統的各個角度和層次來進行優化,以下是需要優化的幾個層次。

從上面我們可以看到,除了jvm調優以外,還有其他幾個層面需要來處理,所以針對系統的調優不是隻有jvm調優一項,而是需要針對系統來整體調優,才能提升系統的性能。本篇只針對jvm調優來講解,其他幾個方面,後續再介紹。

在進行jvm調優之前,我們假設項目的架構調優和代碼調優已經進行過或者是針對當前項目是最優的。這兩個是jvm調優的基礎,並且架構調優是對系統影響最大的 ,我們不能指望一個系統架構有缺陷或者代碼層次優化沒有窮盡的應用,通過jvm調優令其達到一個質的飛躍,這是不可能的。

另外,在調優之前,必須得有明確的性能優化目標, 然後找到其性能瓶頸。之後針對瓶頸的優化,還需要對應用進行壓力和基準測試,通過各種監控和統計工具,確認調優後的應用是否已經達到相關目標。

二、jvm調優流程

調優的最終目的都是為了令應用程序使用最小的硬件消耗來承載更大的吞吐。jvm的調優也不例外,jvm調優主要是針對垃圾收集器的收集性能優化,令運行在虛擬機上的應用能夠使用更少的內存以及延遲獲取更大的吞吐量。當然這裡的最少是最優的選擇,而不是越少越好。

1、性能定義

要查找和評估器性能瓶頸,首先要知道性能定義,對於jvm調優來說,我們需要知道以下三個定義屬性,依作為評估基礎:

  • 吞吐量:重要指標之一,是指不考慮垃圾收集引起的停頓時間或內存消耗,垃圾收集器能支撐應用達到的最高性能指標。
  • 延遲:其度量標準是縮短由於垃圾啊收集引起的停頓時間或者完全消除因垃圾收集所引起的停頓,避免應用運行時發生抖動。
  • 內存佔用:垃圾收集器流暢運行所需要 的內存數量。

這三個屬性中,其中一個任何一個屬性性能的提高,幾乎都是以另外一個或者兩個屬性性能的損失作代價,不可兼得,具體某一個屬性或者兩個屬性的性能對應用來說比較重要,要基於應用的業務需求來確定。

2、性能調優原則

在調優過程中,我們應該謹記以下3個原則,以便幫助我們更輕鬆的完成垃圾收集的調優,從而達到應用程序的性能要求。

1. MinorGC回收原則: 每次minor GC 都要儘可能多的收集垃圾對象。以減少應用程序發生Full GC的頻率。2. GC內存最大化原則:處理吞吐量和延遲問題時候,垃圾處理器能使用的內存越大,垃圾收集的效果越好,應用程序也會越來越流暢。3. GC調優3選2原則: 在性能屬性裡面,吞吐量、延遲、內存佔用,我們只能選擇其中兩個進行調優,不可三者兼得。

3、性能調優流程

以上就是對應用程序進行jvm調優的基本流程,我們可以看到,jvm調優是根據性能測試結果不斷優化配置而多次迭代的過程。在達到每一個系統需求指標之前,之前的每個步驟都有可能經歷多次迭代。有時候為了達到某一方面的指標,有可能需要對之前的參數進行多次調整,進而需要把之前的所有步驟重新測試一遍。

另外調優一般是從滿足程序的內存使用需求開始的,之後是時間延遲的要求,最後才是吞吐量的要求,要基於這個步驟來不斷優化,每一個步驟都是進行下一步的基礎,不可逆行之。以下我們針對每個步驟進行詳細的示例講解。

在JVM的運行模式方面,我們直接選擇server模式,這也是jdk1.6以後官方推薦的模式。

在垃圾收集器方面,我們直接採用了jdk1.6-1.8 中默認的parallel收集器(新生代採用parallelGC,老生代採用parallelOldGC)。

三、確定內存佔用

在確定內存佔用之前,我們需要知道兩個知識點:

  1. 應用程序的運行階段
  2. jvm內存分配

1、運行階段

應用程序的運行階段,我可以劃分為以下三個階段:

1、初始化階段 : jvm加載應用程序,初始化應用程序的主要模塊和數據。2、穩定階段:應用在此時運行了大多數時間,經歷過壓力測試的之後,各項性能參數呈穩定狀態。核心函數被執行,已經被jit編譯預熱過。3、總結階段:最後的總結階段,進行一些基準測試,生成響應的策報告。這個階段我們可以不關注。

確定內存佔用以及活躍數據的大小,我們應該是在程序的穩定階段來進行確定,而不是在項目起初階段來進行確定,如何確定,我們先看以下jvm的內存分配。

2、jvm內存分配&參數

jvm堆中主要的空間,就是以上新生代、老生代、永久代組成,整個堆大小=新生代大小 + 老生代大小 + 永久代大小。 具體的對象提升方式,這裡不再過多介紹了,我們看下一些jvm命令參數,對堆大小的指定。如果不採用以下參數進行指定的話,虛擬機會自動選擇合適的值,同時也會基於系統的開銷自動調整。

在設置的時候,如果關注性能開銷的話,應儘量把永久代的初始值與最大值設置為同一值,因為永久代的大小調整需要進行FullGC 才能實現。

3、計算活躍數據大小

計算活躍數據大小應該遵循以下流程:

如前所述,活躍數據應該是基於應用程序穩定階段時,觀察長期存活與對象在java堆中佔用的空間大小。

計算活躍數據時應該確保以下條件發生:

1.測試時,啟動參數採用jvm默認參數,不人為設置。2.確保Full GC 發生時,應用程序正處於穩定階段。

採用jvm默認參數啟動,是為了觀察應用程序在穩定階段的所需要的內存使用。

如何才算穩定階段?

一定得需要產生足夠的壓力,找到應用程序和生產環境高峰符合狀態類似的負荷,在此之後達到峰值之後,保持一個穩定的狀態,才算是一個穩定階段。所以要達到穩定階段,壓力測試是必不可少的,具體如何如何對應用壓力測試,本篇不過多說明,後期會有專門介紹的篇幅。

在確定了應用出於穩定階段的時候,要注意觀察應用的GC日誌,特別是Full GC 日誌。

GC日誌指令: -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>GC日誌是收集調優所需信息的最好途徑,即便是在生產環境,也可以開啟GC日誌來定位問題,開啟GC日誌對性能的影響極小,卻可以提供豐富數據。/<filename>

必須得有FullGC 日誌,如果沒有的話,可以採用監控工具強制調用一次,或者採用以下命令,亦可以觸發

jmap -histo:live pid

在穩定階段觸發了FullGC我們一般會拿到如下信息:

從以上gc日誌中,我們大概可以分析到,在發生fullGC之時,整個應用的堆佔用以及GC時間,當然了,為了更加精確,應該多收集幾次,獲取一個平均值。或者是採用耗時最長的一次FullGC來進行估算。

在上圖中,fullGC之後,老年代空間佔用在93168kb(約93MB),我們以此定為老年代空間的活躍數據。

其他堆空間的分配,基於以下規則來進行。

基於以上規則和上圖中的FullGC信息,我們現在可以規劃的該應用堆空間為:

java 堆空間: 373Mb (=老年代空間93168kb*4)新生代空間:140Mb(=老年代空間93168kb*1.5)永久代空間:5Mb(=永久代空間3135kb*1.5)老年代空間: 233Mb=堆空間-新生代看空間=373Mb-140Mb

對應的應用啟動參數應該為:

四、延遲調優

在確定了應用程序的活躍數據大小之後,我們需要再進行延遲性調優,因為對於此時堆內存大小,延遲性需求無法達到應用的需要,需要基於應用的情況來進行調試。

在這一步進行期間,我們可能會再次優化堆大小的配置,評估GC的持續時間和頻率、以及是否需要切換到不同的垃圾收集器上。

1、系統延遲需求

在調優之前,我們需要知道系統的延遲需求是那些,以及對應的延遲可調優指標是那些。

  • 應用程序可接受的平均停滯時間: 此時間與測量的Minor GC持續時間進行比較。
  • 可接受的Minor GC頻率:Minor GC的頻率與可容忍的值進行比較。
  • 可接受的最大停頓時間: 最大停頓時間與最差情況下FullGC的持續時間進行比較。
  • 可接受的最大停頓發生的頻率:基本就是FullGC的頻率。

以上中,平均停滯時間和最大停頓時間,對用戶體驗最為重要,可以多關注。

基於以上的要求,我們需要統計以下數據:

  • MinorGC的持續時間;
  • 統計MinorGC的次數;
  • FullGC的最差持續時間;
  • 最差情況下,FullGC的頻率;

2、優化新生代的大小

比如如上的gc日誌中,我們可以看到Minor GC的平均持續時間=0.069秒,MinorGC 的頻率為0.389秒一次。

如果,我們系統的設置的平均停滯時間為50ms,當前的69ms明顯是太長了,就需要調整。我們知道新生代空間越大,Minor GC的GC時間越長,頻率越低。如果想減少其持續時長,就需要減少其空間大小。如果想減小其頻率,就需要加大其空間大小。

為了降低改變新生代的大小對其他區域的最小影響。在改變新生代空間大小的時候,儘量保持老年代空間的大小。

比如此次減少了新生代空間10%的大小,應該保持老年代和持代的大小不變化,第一步調優後的參數如下變化:

3、優化老年代的大小

同上一步一樣,在優化之前,也需要採集gc日誌的數據。此次我們關注的是FullGC的持續時間和頻率。

上圖中,我們可以看到

如果沒有FullGC的日誌,有辦法可以評估麼?

我們可以通過對象提升率進行計算。

對象提升率

比如上述中啟動參數中,我們的老年代大小=233Mb。

那麼需要多久才能填滿老年代中這233Mb的空閒空間取決於新生代到老年代的提升率。

每次提升老年代佔用量=每次MinorGC 之後 java堆佔用情況 減去 MinorGC後新生代的空間佔用對象提升率=平均值(每次提升老年代佔用量) 除以 老年代空間

有了對象提升率,我們就可以算出填充滿老年代空間需要多少次minorGC,大概一次fullGC的時間就可以計算出來了。

比如:

上圖中:

老年代每次minorGC提升率

我們可以測算出:

FullGC的預期最差頻率時長可以通過以上兩種方式估算出來,可以調整老年代的大小來調整FullGC的頻率,當然了,如果FullGC持續時間過長,無法達到應用程序的最差延遲要求,就需要切換垃圾處理器了。具體如何切換,下篇再講,比如切換為CMS,針對CMS的調優方式又有會細微的差別。

五、吞吐量調優

經過上述漫長 調優過程,最終來到了調優的最後一步,這一步對上述的結果進行吞吐量測試,並進行微調。

吞吐量調優主要是基於應用程序的吞吐量要求而來的,應用程序應該有一個綜合的吞吐指標,這個指標基於真個應用的需求和測試而衍生出來的。當有應用程序的吞吐量達到或者超過預期的吞吐目標,整個調優過程就可以圓滿結束了。

如果出現調優後依然無法達到應用程序的吞吐目標,需要重新回顧吞吐要求,評估當前吞吐量和目標差距是否巨大,如果在20%左右,可以修改參數,加大內存,再次從頭調試,如果巨大就需要從整個應用層面來考慮,設計以及目標是否一致了,重新評估吞吐目標。

對於垃圾收集器來說,提升吞吐量的性能調優的目標就是就是儘可能避免或者很少發生FullGC 或者Stop-The-World壓縮式垃圾收集(CMS),因為這兩種方式都會造成應用程序吞吐降低。儘量在MinorGC 階段回收更多的對象,避免對象提升過快到老年代。

六、最後

歡迎大家關注我的主頁,作為一名架構師,我每天都會分享一些有關Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成為架構師必備的知識體系。

點擊我的頭條,私信【java】還可以領取免費的學習資源和前輩的面試經驗和麵試題,相信對於已經工作或遇到技術瓶頸的碼友,會有你需要的內容。

據Plumbr公司對特定垃圾收集器使用情況進行了一次調查研究,研究數據使用了84936個案例。在明確指定垃圾收集器的13%的案例中,併發收集器(CMS)使用次數最多;但大多數案例沒有選擇最佳垃圾收集器。這個比例佔用在87%左右。

JVM調優是一個系統而又複雜的工作,目前jvm下的自動調整已經做的比較優秀,基本的一些初始參數都可以保證一般的應用跑的比較穩定了,對部分團隊來說,程序性能可能優先級不高,默認垃圾收集器已經夠用了。調優要基於自己的情況而來。


Java高併發框架


首先,所有的調優或優化都要有一個目的指標,也就是現在是什麼樣,要達到什麼樣,不能為了調優而調優!

其次,在有目標指標參數後,可以通過一些命令如jstat、jinfo等查看一些參數及JVM信息,確認這些配置參數後,可以獲取JVM的堆轉儲快照文件,即dump文件,通過編輯器查看分析日誌信息或一些可視化工具來分析性能的瓶頸,通過分析得出一些可能需要調整的參數,如堆大小、垃圾收集器參數,調整這麼參數,觀察運行情況,或再次分析dump文件。

希望對你有所幫助!


Java實戰技術


堆大小設置

JVM 中最大堆大小有三方面限制:相關操作系統的數據模型(32-bt還是64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位系統下,一般限制在1.5G~2G;64為操作系統對內存無限制。我在Windows Server 2003 系統,3.5G物理內存,JDK5.0下測試,最大可設置為1478m。


非同11


jvm調優,一定是根據你的程序運行的具體情況來進行調優的,而不是看調優指南,改改參數就可以的。


分享到:


相關文章: