JVM調優實戰

 

 

 

 

 

 

 

 

 

JVM調優實戰 java

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

文檔修訂記錄 程序員

版本web

日期算法

撰寫人數據庫

審覈人windows

批准人數組

變動摘要 & 修訂位置瀏覽器

           
           
           
           
           
           
           
           
           
           
           
           
           
           
           

 

目錄緩存

1    理論篇    1 服務器

1.1    多功能養魚塘-JVM內存    1

1.2    池塘中的魚-程序中的對象    2

1.3    養殖區域劃分-JVM中的代    2

1.4    主人按期捕魚-JVM垃圾回收    4

1.5    不一樣的捕魚方式-垃圾回收器    5

1.6    捕魚工具選擇-JVM參數    7

2    實戰篇    16

2.1    測試目的    16

2.2    測試環境準備    16

2.3    錄製測試腳本    17

2.4    定義測試場景    17

2.5    執行初步性能測試    17

2.6    選擇調優方案    18

2.7    調優後JVM監控圖    21

2.8    測試結果分析    24

3    性能問題舉例    25

3.1    性能症狀    25

3.2    監控結果    25

3.3    緣由分析    28

3.4    該系統的JVM設置    29

4    後記    29

5    附:捨得網的典型配置    30

 

  1. 理論篇

  1. 多功能養魚塘-JVM內存

大魚塘O(可分配內存): JVM能夠調度使用的總的內存數,這個數量受操做系統進程尋址範圍、系統虛擬內存總數、系統物理內存總數、其餘系統運行所佔用的內存資源等因素的制約。

小池塘A(堆內存):JVM運行時數據區域,它爲類實例和數組分配的內存。堆能夠是固定大小的也能夠是可變大小的。其中 Heap = {Old + NEW = { Eden , from, to } }。

小池塘B(非堆內存):包括全部線程之間共享的一個方法區域和JVM爲優化或內部處理所分配的內存。它存儲每個類的結構,如一個運行時的常量池、字段和方法數據、方法的代碼和構造函數。這個方法區是邏輯上堆的一部分,但依賴於實現,一個JVM能夠不去回收或者壓縮它。像堆同樣,方法區能夠固定大小的,也能夠是大小可變的。方法區不是必須是連續的,它們能夠是不連續的。除方法區以外,JVM老是從非堆中分配用於優化和內部處理所需的內存。例如,JIT編譯器爲高性能的JVM代碼轉換存儲成本地代碼而分配的內存。

整個池塘結構圖以下:

查看大池塘O大小的方法爲:

在命令行下用 java -XmxXXXXM -version 命令來進行測試,而後逐漸的增大XXXX的值,若是執行正常就表示指定的內存大小可用,不然會打印錯誤信息,示例以下:

java -Xmx3072M -version。

當一個URL被訪問時,內存申請過程以下:

A. JVM會試圖爲相關Java對象在Eden中初始化一塊內存區域

B. 當Eden空間足夠時,內存申請結束。不然到下一步

C. JVM試圖釋放在Eden中全部不活躍的對象(這屬於1或更高級的垃圾回收), 釋放後若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區

D. Survivor區被用來做爲Eden及OLD的中間交換區域,當OLD區空間足夠時,Survivor區的對象會被移到Old區,不然會被保留在Survivor區

E. 當OLD區空間不夠時,JVM會在OLD區進行徹底的垃圾收集(0級)

F. 徹底垃圾收集後,若Survivor及OLD區仍然沒法存放從Eden複製過來的部分對象,致使JVM沒法在Eden區爲新對象建立內存區域,則出現"out of memory錯誤"

  1. 池塘中的魚-程序中的對象

程序中運行的各類類實例稱之爲對象,每一個對象都有不一樣的生命週期,有的存活時間長點,有的存活時間短點,這就想魚塘中養的不一樣生長期的魚同樣,有的三個月就能夠上市,有的魚則須要6個月甚至更長的時間才能上市。JVM內存機制的設置就是爲了要知足這種不一樣生命週期的對象對內存的需求,並使之能達到最大的性能表現。

  1. 養殖區域劃分-JVM中的代

魚塘主人爲了充分利用現有的條件來賺取更多的利潤,他須要餵養各類不一樣種類的魚,因而又把魚塘分割成了幾塊不一樣區域:"魚苗養殖區"、"短中期養殖區"、"長期養殖區",來養殖不一樣生長週期的魚。JVM一樣爲了對各類不一樣生命週期的對象進行有效管理也劃分了各類不一樣的區域,這就是"代"的概念,分別叫作:"青年代"、"老年代"、"持久代",下面逐一介紹每一個代的含義和做用。

短中期魚苗養殖區-年青代(Young Generation)

年青代由一個Eden Space和兩個Survivor Spaces組成,虛擬機初始時分配全部的對象到Eden Space,許多對象也是在這裏死去。當它執行一個"minor GC"的時候,虛擬機將從Eden Space中移動一些殘餘的對象到其中的一個Survivor Spaces中。青年代就好像養魚塘中的"中短時間養殖區"同樣,主人把魚先投放到"短時間養殖區"餵養,隔一段時間就開始下網撈出已經長成的那些魚拿到集市去賣,這個過程就是從"Eden Space"中執行垃圾回收的過程。主人接着把捕撈以後剩下的"漏網之魚"趕到"中期養殖區"繼續餵養。這個"中期養殖區"就是"Survivor Spaces",固然魚在"中期養殖區"餵養一段時間後也要撈出那些長成的魚去賣,這就是對"Survivor Spaces" 執行垃圾回收的過程。

Ps Eden Space: 這個內存池在對象初始化時被分配;

Ps Survivor Space: 這個內存池中包含着Eden Space 通過GC以後倖存下來的對象;

年輕代設置策略:對於響應時間優先的應用需儘量設大,直到接近系統的最低響應時間限制(根據實際狀況選擇)。在此種狀況下,年輕代收集發生的頻率也是最小的。同時,減小到達老年代的對象。對於吞吐量優先的應用則儘量的設置大,可達到Gbit的程度。由於對響應時間沒有要求,垃圾收集能夠並行進行,通常適合8CPU以上的應用。

長期養殖區-老年代(老年代):、

虛擬機將在Survivor Spaces中生存足夠長時間的對象移動到老年代的Tenured Spaces中。當Tenured Generation被填滿,則將執行一個徹底GC,這個徹底GC很是的慢,由於它要處理全部存活着的對象,用的是串行標記收集的方式,併發收集能夠減小對於應用的影響。

老年代設置策略:對於響應時間優先的應用,老年代使用併發收集器,因此其大小須要當心設置,通常要考慮併發會話率會話持續時間等一些參數。若是堆設置小了,可能會形成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;若是堆大了,則須要較長的收集時間。

最優化的方案,通常須要參考如下數據得到:

  • 併發垃圾收集信息
  • 持久代併發收集次數
  • 傳統GC信息
  • 花在年輕代和老年代回收上的時間比例
  • 減小年輕代和老年代花費的時間,通常會提升應用的效率

對於吞吐量優先的應用,通常吞吐量優先的應用都有一個很大的年輕代和一個較小的老年代。緣由是,這樣能夠儘量回收掉大部分短時間對象,減小中期的對象,而老年代盡存放長期存活對象。

較小堆引發的碎片問題 :由於老年代的併發收集器使用標記、清除算法,因此不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合併,這樣能夠分配給較大的對象。可是,當堆空間較小時,運行一段時間之後,就會出現"碎片",若是併發收集器找不到足夠的空間,那麼併發收集器將會中止,而後使用傳統的標記、清除方式進行回收。若是出現"碎片",可能須要進行以下配置: -XX:+UseCMSCompactAtFullCollection 使用併發收集器時,開啓對老年代的壓縮;-XX:CMSFullGCsBeforeCompaction=0上面配置開啓的狀況下,這裏設置多少次Full GC後,對老年代進行壓縮。

/*監控實例*/

內存池名稱: Tenured Gen

Java 虛擬機最初向操做系統請求的內存量: 3,538,944 字節

Java 虛擬機實際能從操做系統得到的內存量: 1,431,699,456 字節

Java 虛擬機可從操做系統得到的最大內存量: 1,431,699,456 字節。請注意,並不必定能得到該內存量。

Java 虛擬機此時使用的內存量: 1,408,650,472 字節

/*監控實例*/

實例說明:系統能得到的最大Tenured Generation空間大小爲1.431G左右,此時使用已經1.408G,基本滿了,因此在JVM執行串行標記垃圾收集時,系統響應速度會很慢!

魚苗養殖區-持久代(Permanent Generation)

控制着全部虛擬機本身映射的數據,如類和對象的方法。在持久代中jvm則存儲class和method對象。持久代就像魚苗養殖區同樣,池塘主人一次對該區域投入足夠量的魚苗,已保證其餘魚塘的足夠供應。就配置而言,永久域是一個獨立域而且不認爲是堆的一部分。永久域默認大小爲4m.運行程序時,jvm會調整永久域的大小以知足須要。每次調整時,jvm會對堆進行一次徹底的垃圾收集。 使用-XX:MaxPerSize標誌來增長永久域搭大小。在WebLogic Server應用程序加載較多類時,常常須要增長永久域的最大值。當jvm加載類時,永久域中的對象急劇增長,從而使jvm不斷調整永久域大小。爲了不調整,可以使用-XX:PerSize標誌設置初始值。

  1. 主人按期捕魚-JVM垃圾回收

一個池塘收容積限制,能養殖的魚的數量是必定的,所以隔一段時間必須撈出部分長成的魚來使主人能餵養不少的魚。一樣,JVM所管理的有限內存也要實現最優化利用,Garbage Collection(GC)就是用來釋放沒有被引用的對象所佔領的內存,目的在於清除再也不使用的對象。GC經過算法和參數的配置能夠對性能產生效果顯著的影響。

GC就好像把長成的魚從池塘中撈出來拿到市場上去賣,而後給池塘騰出空間繼續養別的魚賺錢。採用哪一種養殖方式能讓魚塘主人賺到更大的利潤是魚塘主人的經營目的,而JVM調優的目的在於如何能是系統表現出更好的響應時間、更大的吞吐量。

Minor Collections(局部垃圾回收):當通用內存消耗完被分配的內存時,JVM會在內存池上執行一個局部的GC(老是調用minor collection)去釋放被dead的對象所佔用的內存。這個局部的GC一般比徹底GC要快許多。青年代中的垃圾回收就是採用局部垃圾回收機制,所以,青年代中內存分配和管理效率也是最高。

一般狀況下,對於內存的申請優先在青年代中申請,當內存不夠時會整理新生代,當整理之後仍是不能知足申請的內存,就會向老年代移動一些生命週期較長的對象。這種整理和移動會消耗資源,同時下降系統運行響應能力,所以若是青年代設置的太小,就會頻繁的整理和移動,對性能形成影響。那是否把年青代設置的越大越好,其實否則,青年代採用的是複製蒐集算法,這種算法必須中止全部應用程序線程,服務器線程切換時間就會成爲應用響應的瓶頸。

Major Collections(徹底垃圾回收):當老年代須要被回收,這就是一個major collection ,它的運行經常很是慢,由於它要涉及全部存活着的類。

/*實例*/

垃圾收集器的名稱: Copy

使用此垃圾收集器收集的數量: 219 字節

垃圾收集時間: 18 秒 630 毫秒

垃圾收集器的名稱: MarkSweepCompact

使用此垃圾收集器收集的數量: 47 字節

垃圾收集時間: 36 秒 166 毫秒

實例說明:copy垃圾蒐集器的運行時間爲18秒回收219字節,回收速度爲平均每秒12字節,而MKC垃圾蒐集器的時間爲36秒回收了47字節,回收速度爲平均每秒1.3字節,二者差距幾乎達到了10倍,可見徹底垃圾回收的速度遠不如局部垃圾回收。

  1. 不一樣的捕魚方式-垃圾回收器

Sun JVM提供有4垃圾回收器:

Serial Collector(序列垃圾回收器):垃圾回收器對Young Gen和Tenured Gen都是使用單線的垃圾回收方式,對Young Gen,會使用拷貝策略避免內存碎片,對Old Gen,會使用壓縮策略避免內存碎片。在JVM啓動參數中使用-XX:+UseSerialGC啓用Serial Collector。串行收集器只適用於小數據量的狀況,默認狀況下,JDK5.0之前都是使用串行收集器,若是想使用其餘收集器須要在啓動時加入相應參數。基本上在多內核的服務器上應該避免使用這種方式。JDK5.0之後,JVM會根據當前系統配置進行判斷。串行GC適合小型應用和單處理器系統(無需多線程交互,效率比較高)。

Parallel Collector(併發垃圾回收器):垃圾回收器對Young Gen和Tenured Gen都是使用多線程並行垃圾回收的方式,對Young Gen,會使用拷貝策略避免內存碎片,對Old Gen,會使用壓縮策略避免內存碎片。在JVM啓動參數中使用-XX:+UseParallelGC啓用Parallel Collector。這是一種吞吐量優先的並行收集器 ,主要以到達必定的吞吐量爲目標,適用於科學技術和後臺處理等。採用了多線程並行管理和回收垃圾對象,提升了回收效率和服務器的吞吐量,適合於多處理器的服務器。

Parallel Compacting Collector(並行壓縮垃圾回收器):與Parallel Collector垃圾回收相似,但對Tenured Gen會使用一種更有效的垃圾回收策略,此垃圾回收器在暫停時間上會更短。在JVM啓動參數中使用-XX:+UseParallelOldGC啓用Parallel Compacting Collector。這是一種響應時間優先的併發收集器 ,主要是保證系統的響應時間,減小垃圾收集時的停頓時間。適用於應用服務器、電信領域等。

Concurrent Mark-Sweep (CMS) Collector(併發標誌清除垃圾回收器):對Young Gen會使用與Parallel Collector一樣的垃圾回收策略,對Tenured Gen垃圾回收的垃圾標誌線程與應用線程同時進行,而垃圾清除則須要暫停應用線程,但暫停時間會大大縮減,須要注意的是,因爲垃圾回收過程更加複雜,會下降整體的吞吐量。

這裏說一下並行和併發的區別,並行指的是多個進程並行執行垃圾回收,那麼能夠很好的利用多處理器,而併發指的是應用程序不須要暫停能夠和垃圾回收線程併發工做。

說明:對於聯機處理的應用系統或複雜的3層應用系統,採用Concurrent Mark-Sweep (CMS) Collector進行垃圾蒐集,基本上既能保證性能,又能保證穩定性(暫停時間短)。

  1. 捕魚工具選擇-JVM參數

  2. 通用JVM參數

-server

若是不配置該參數,JVM會根據應用服務器硬件配置自動選擇不一樣模式,server模式啓動比較慢,可是運行期速度獲得了優化,適合於服務器端運行的JVM。

-client

啓動比較快,可是運行期響應沒有server模式的優化,適合於我的PC的服務開發和測試。

-Xmx

設置java heap的最大值,默認是機器物理內存的1/4。這個值決定了最多可用的Java堆內存:分配過少就會在應用中須要大量內存做緩存或者臨時對象時出現OOM(Out Of Memory)的問題;若是分配過大,那麼就會因PermSize太小而引發的另一種Out Of Memory。因此如何配置仍是根據運行過程當中的分析和計算來肯定,若是不能肯定仍是採用默認的配置。

-Xms

設置Java堆初始化時的大小,默認狀況是機器物理內存的1/64。這個主要是根據應用啓動時消耗的資源決定,分配少了申請起來會下降運行速度,分配多了也浪費。

-XX:PermSize

初始化永久內存區域大小。永久內存區域全稱是Permanent Generation space,是指內存的永久保存區域,程序運行期不對PermGen space進行清理,因此若是你的APP會LOAD不少CLASS的話,就極可能出現PermGen space錯誤。這種錯誤常見在web服務器對JSP進行pre compile的時候。 若是你的WEB APP下用了大量的第三方jar,其大小超過了jvm默認的PermSize大小(4M)那麼就會產生此錯誤信息了。

-XX:MaxPermSize

設置永久內存區域最大大小。

-Xmn

直接設置青年代大小。整個JVM可用內存大小=青年代大小 + 老年代大小 + 持久代大小 。持久代通常固定大小爲64m,因此增大年輕代後,將會減少老年代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。

按照Sun的官方設置比例,則上面的例子中年輕代的大小應該爲2048*3/8=768M。

-XX:NewRatio

控制默認的Young代的大小,例如,設置-XX:NewRatio=3意味着Young代和老年代的比率是1:3。換句話說,Eden和Survivor空間總和是整個堆大小的1/4。

如圖中的實際設置,-XX:NewRatio=2,-Xmx=2048,則年輕代和老年代的分配比例爲1:2,即年輕代的大小爲682M,而老年代的大小爲1365M。查看實際系統的jvm監控結果爲:

內存池名稱: Tenured Gen

Java 虛擬機最初向操做系統請求的內存量: 3,538,944 字節

Java 虛擬機實際能從操做系統得到的內存量: 1,431,699,456 字節

Java 虛擬機可從操做系統得到的最大內存量: 1,431,699,456 字節。請注意,並不必定能得到該內存量。

Java 虛擬機此時使用的內存量: 1,408,650,472 字節

即:1,408,650,472 字節=1365M,證實了上面的計算是正確的。

-XX:SurvivorRatio

設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6。越大的survivor空間能夠容許短時間對象儘可能在年青代消亡;若是Survivor空間過小,Copying收集將直接將其轉移到老年代中,這將加快老年代的空間使用速度,引起頻繁的徹底垃圾回收。

以下圖:

SurvivorRatio的值設爲3,Xmn爲768M,則每一個Survivor空間的大小爲768M/5=153.6M。

-XX:NewSize

爲了實現更好的性能,您應該對包含短時間存活對象的池的大小進行設置,以使該池中的對象的存活時間不會超過一個垃圾回收循環。新生成的池的大小由 NewSize 和 MaxNewSize 參數肯定。經過這個選項能夠設置Java新對象生產堆內存。在一般狀況下這個選項的數值爲1024的整數倍而且大於1MB。這個值的取值規則爲,通常狀況下這個值-XX:NewSize是最大堆內存(maximum heap size)的四分之一。增長這個選項值的大小是爲了增大較大數量的短生命週期對象。增長Java新對象生產堆內存至關於增長了處理器的數目。而且能夠並行地分配內存,可是請注意內存的垃圾回收倒是不能夠並行處理的。做用跟-XX:NewRatio類似, -XX:NewRatio是設置比例而-XX:NewSize是設置精確的數值。

-XX:MaxNewSize

經過這個選項能夠設置最大Java新對象生產堆內存。一般狀況下這個選項的數值爲1 024的整數倍而且大於1MB,其功用與上面的設置新對象生產堆內存-XX:NewSize相同。通常要將NewSize和MaxNewSize設成一致。

-XX:MaxTenuringThreshold

設置垃圾最大年齡。若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入老年代。對於老年代比較多的應用,能夠提升效率。若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象在年輕代的存活時間,增長在年輕代即被回收的機率。

以下圖:

-XX:MaxTenuringThreshold參數被設置成5,表示對象會在Survivor區進行5次複製後若是尚未被回收纔會被複制到老年代。

-XX:GCTimeRatio

設置垃圾回收時間佔程序運行時間的百分比。該參數設置爲n的話,則垃圾回收時間佔程序運行時間百分比的公式爲1/(1+n) ,若是n=19表示java能夠用5%的時間來作垃圾回收,1/(1+19)=1/20=5%。

-XX:TargetsurvivorRatio

該值是一個百分比,控制容許使用的救助空間的比例,默認值是50。該參數設置較大的話可提升對survivor空間的使用率。當較大的堆棧使用較低的SurvivorRatio時,應增長該值到80至90,以更好利用救助空間。

-Xss

設置每一個線程的堆棧大小,根據應用的線程所需內存大小進行調整,在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。當這個選項被設置的較大(>2MB)時將會在很大程度上下降系統的性能。所以在設置這個值時應該格外當心,調整後要注意觀察系統的性能,不斷調整以期達到最優。

JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K。

-Xnoclassgc

這個選項用來取消系統對特定類的垃圾回收。它能夠防止當這個類的全部引用丟失以後,這個類仍被引用時不會再一次被從新裝載,所以這個選項將增大系統堆內存的空間。禁用類垃圾回收,性能會高一點;

  1. 串行收集器參數

-XX:+UseSerialGC:

設置串行收集器 。

  1. 並行收集器參數

-XX:+UseParallelGC:

選擇垃圾收集器爲並行收集器,此配置僅對年輕代有效,即上述配置下,年輕代使用並行收集,而老年代仍舊使用串行收集。採用了多線程並行管理和回收垃圾對象,提升了回收效率,提升了服務器的吞吐量,適合於多處理器的服務器。

-XX:ParallelGCThreads

配置並行收集器的線程數,即:同時多少個線程一塊兒進行垃圾回收。此值最好配置與處理器數目相等。

-XX:+UseParallelOldGC:

採用對於老年代併發收集的策略,能夠提升收集效率。JDK6.0支持對老年代並行收集。

-XX:MaxGCPauseMillis

設置每次年輕代並行收集最大暫停時間,若是沒法知足此時間,JVM會自動調全年輕代大小以知足此值。

-XX:+UseAdaptiveSizePolicy:

設置此選項後,並行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低響應時間或者收集頻率等,此值建議使用並行收集器時,一直打開。

  1. 併發收集器參數

-XX:+UseConcMarkSweepGC

指定在 老年代 使用 concurrent cmark sweep gc。gc thread 和 app thread 並行 ( 在 init-mark 和 remark 時 pause app thread)。app pause 時間較短 , 適合交互性強的系統 , 如 web server。它能夠併發執行收集操做,下降應用中止時間,同時它也是並行處理模式,能夠有效地利用多處理器的系統的多進程處理。

-XX:+UseParNewGC

指定在 New Generation 使用 parallel collector, 是 UseParallelGC 的 gc 的升級版本 , 有更好的性能或者優勢 , 能夠和 CMS gc 一塊兒使用

-XX:+UseCMSCompactAtFullCollection:

打開對老年代的壓縮。可能會影響性能,可是能夠消除碎片,在FULL GC的時候, 壓縮內存, CMS是不會移動內存的, 所以, 這個很是容易產生碎片, 致使內存不夠用, 所以, 內存的壓縮這個時候就會被啓用。 增長這個參數是個好習慣。

-XX:+CMSIncrementalMode:

設置爲增量模式。適用於單CPU狀況

-XX:CMSFullGCsBeforeCompaction

因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生"碎片",使得運行效率下降。此值設置運行多少次GC之後對內存空間進行壓縮、整理。

-XX:+CMSClassUnloadingEnabled

使CMS收集持久代的類,而不是fullgc

-XX:+CMSPermGenSweepingEnabled

使CMS收集持久代的類,而不是fullgc。

-XX:-CMSParallelRemarkEnabled

在使用 UseParNewGC 的狀況下 , 儘可能減小 mark 的時間。

-XX:CMSInitiatingOccupancyFraction

說明老年代到百分之多少滿的時候開始執行對老年代的併發垃圾回收(CMS),這個參數設置有很大技巧,基本上知足公式:

(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn

時就不會出現promotion failed。在個人應用中Xmx是6000,Xmn是500,那麼Xmx-Xmn是5500兆,也就是老年代有5500兆,CMSInitiatingOccupancyFraction=90說明老年代到90%滿的時候開始執行對老年代的併發垃圾回收(CMS),這時還剩10%的空間是5500*10%=550兆,因此即便Xmn(也就是年輕代共500兆)裏全部對象都搬到老年代裏,550兆的空間也足夠了,因此只要知足上面的公式,就不會出現垃圾回收時的promotion failed;

若是按照Xmx=2048,Xmn=768的比例計算,則CMSInitiatingOccupancyFraction的值不能超過40,不然就容易出現垃圾回收時的promotion failed。

-XX:+UseCMSInitiatingOccupancyOnly

指示只有在老年代在使用了初始化的比例後 concurrent collector 啓動收集

-XX:SoftRefLRUPolicyMSPerMB

相對於客戶端模式的虛擬機(-client選項),當使用服務器模式的虛擬機時(-server選項),對於軟引用(soft reference)的清理力度要稍微差一些。能夠經過增大-XX:SoftRefLRUPolicyMSPerMB來下降收集頻率。默認值是 1000,也就是說每秒一兆字節。Soft reference在虛擬機中比在客戶集中存活的更長一些。其清除頻率能夠用命令行參數 -XX:SoftRefLRUPolicyMSPerMB=<N> 來控制,這能夠指定每兆堆空閒空間的 soft reference 保持存活(一旦它不強可達了)的毫秒數,這意味着每兆堆中的空閒空間中的 soft reference 會(在最後一個強引用被回收以後)存活1秒鐘。注意,這是一個近似的值,由於 soft reference 只會在垃圾回收時纔會被清除,而垃圾回收並不總在發生。

-XX:LargePageSizeInBytes

內存頁的大小, 不可設置過大,會影響Perm的大小。

-XX:+UseFastAccessorMethods

原始類型的快速優化,get,set 方法轉成本地代碼。

-XX:+DisableExplicitGC 

禁止 java 程序中的 full gc, 如 System.gc() 的調用。 最好加上防止程序在代碼裏誤用了,對性能形成衝擊。

-XX:+AggressiveHeap

特別說明下:(我感受對於作java cache應用有幫助)

試圖是使用大量的物理內存

長時間大內存使用的優化,能檢查計算資源(內存, 處理器數量)

至少須要256MB內存

大量的CPU/內存, (在1.4.1在4CPU的機器上已經顯示有提高)

-XX:+AggressiveOpts

加快編譯

-XX:+UseBiasedLocking

鎖機制的性能改善。

  1. 實戰篇

    1. 測試目的

測試被測系統使用不一樣的垃圾回收方案時的性能表現;

瞭解各類JVM參數在性能調優時的實際效果;

對遴選出的最優方案進行8小時壓力測試並記錄測試結果;

  1. 測試環境準備

被測程序的運行的軟硬件環境:

  • D630 4G內存+T7250雙核CPU+160G硬盤;
  • 操做系統:windowsXP SP3;
  • IP:11.55.15.51;

被測程序名稱:

  • XXX銀行採購管理系統V1.1版;

程序部署環境:

  • Tomcat6.0.18 for windows;
  • Sun JDK1.6.13 for windows;
  • Oracle10g for windows(單獨運行在另一臺640M筆記本上)

性能測試工具運行的軟硬件環境:

  • 操做系統:windowsxp sp3
  • 瀏覽器版本:IE7
  • IP地址:11.55.15.141
  • 性能測試工具:loadrunner9.10

JVM監控工具:

  • 使用Jconsole進行圖形化監控;

肯定JVM在被測系統的機器上最大可用內存:

經過在命令行下用 java -XmxXXXXM -version 命令反覆測試發如今11.55.15.51機器上JVM能使用的最大內存爲1592M。

  1. 錄製測試腳本

錄製前準備:修改checkcode.java文件,將隨機生成的校驗碼改爲一個固定的校驗碼方便腳本的自動運行,而後將編譯好的checkcode.class文件替換髮布包中的class文件。

選擇典型業務操做進行腳本錄製,每一個系統的典型業務操做都會不一樣,須要通過分析統計,選擇用戶操做頻率最高的部分。通過分析後肯定的腳本內容爲:錄製系統登陸操做並在登陸成功後的主界面上選取一段文字做爲驗證點。

啓動VuGen程序按腳本定義內容進行錄製並調試腳本,保證腳本能正常運行。

  1. 定義測試場景

  • 虛擬用戶數:30
  • 持續運行時間:8小時
  • 虛擬用戶加載和卸載方式:同時
  • 性能監控指標:響應時間、吞吐量、成功交易數
  1. 執行初步性能測試

使用系統默認的參數執行測試,並記錄響應時間、吞吐量已經成功交易數等數據,同時監控JVM的使用狀況。

  1. 選擇調優方案

不一樣垃圾回收方法測試數據:

Id

NewRatio

SurviorRatio

TransResponse Time

Throughput

Passed Transactions

1

2

25

3.139s

3016230.514

7528

2

1

25

3.161s

2975581.301

7452

3

3

25

2.814s

3334717.818

8383

4

4

25

2.659s

3505592.450

8846

5

5

25

2.860s

3270596.069

8232

6

4

15

2.499s

3765121.986

9426

7

4

5

1.986s

4750776.581

11843

8

4

4

1.968s

4825608.161

11947

9

4

3

2.507s

3770420.243

9388

10

-XX:TargetSurvivorRatio=90 

1.924

4945053.874

12216

11

-Xmx1024M

1.903

4974137.908

12360

併發收集模式,運行時間十分鐘後的對內存使用狀況:

串行收集模式,運行時間十分鐘:

並行收集模式,運行時間十分鐘:

30-60:30個併發用戶連續運行60分鐘的jvm內存變化截圖:

在11:36和11:56分發生了兩次徹底GC(Full GC),由於這時PS Old Gen已經滿了,JVM自動對Old Gen中的內存進行了回收。

根據反覆的測試並結合被測系統業務特色,最終敲定了使用如下最優方案進行8小時壓力測試:

JAVA_OPTS=-server

-Xms1024M

-Xmx1024M

-Xmn128M

-XX:NewSize=128M

-XX:MaxNewSize=128M

-XX:SurvivorRatio=20

-XX:MaxTenuringThreshold=10

-XX:GCTimeRatio=19

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+CMSClassUnloadingEnabled

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=70

-XX:SoftRefLRUPolicyMSPerMB=0

–XX:PermSize=256m

-XX:MaxPermSize=256m

-Djava.awt.headless=true

  1. 調優後JVM監控圖

30Vusers運行8小時截圖:

  1. 測試結果分析

對於XX銀行採購系統的登陸操做來講,將jvm的NewRatio 和SurviorRatio設置成4時,性能表現最好!在此基礎上在設置-XX:TargetSurvivorRatio=90和-Xmx1024M後性能也有必定程度的提高。

  1. 性能問題舉例

    1. 性能症狀

XX省一個正式上線運行的系統,每運行一段時間後程序進程會莫名其妙地被kill掉,不得不手工啓動系統。

  1. 監控結果

  2. jmap命令查看堆內存分配和使用狀況

./jmap -heap 31 //31爲程序的進程號

Attaching to process ID 31, please wait...

Debugger attached successfully.

Server compiler detected.

JVM version is 11.0-b12 //顯示jvm的版本號

using parallel threads in the new generation. //說明在年輕代使用了並行收集

using thread-local object allocation.

Concurrent Mark-Sweep GC //啓用CMS收集模式

 

Heap Configuration:

MinHeapFreeRatio = 40

MaxHeapFreeRatio = 70 //這兩項說明堆內存的使用比例在30%~60%之間

MaxHeapSize = 2147483648 (2048.0MB) //最大堆大小爲2048M

NewSize = 805306368 (768.0MB)

MaxNewSize = 805306368 (768.0MB) //年輕代大小爲768M

OldSize = 1342177280 (1280.0MB) //老年代代大小爲1280M

NewRatio = 8 //這個有點自相矛盾,1:8

SurvivorRatio = 3 //救助區大小佔整個年輕代的五分之一

PermSize = 268435456 (256.0MB) //持久代大小爲256M

MaxPermSize = 268435456 (256.0MB) //持久代大小爲256M

 

Heap Usage:

//年輕代大小,這裏只計算了一個救助區,因此少了153M

New Generation (Eden + 1 Survivor Space):

capacity = 644284416 (614.4375MB)

used = 362446760 (345.65616607666016MB)

free = 281837656 (268.78133392333984MB)

56.25570803810968% used

//Eden Space大小爲614.43-153=460.8M

Eden Space:

capacity = 483262464 (460.875MB)

used = 342975440 (327.0868682861328MB)

free = 140287024 (133.7881317138672MB)

70.97084204743864% used

//兩個救助區的大小均爲153MB, 與前面的SurvivorRatio參數設置值計算結果一致。

From Space:

capacity = 161021952 (153.5625MB)

used = 19471320 (18.569297790527344MB)

free = 141550632 (134.99320220947266MB)

12.092338813530219% used

To Space:

capacity = 161021952 (153.5625MB)

used = 0 (0.0MB)

free = 161021952 (153.5625MB)

0.0% used

//老年代大小爲1280M,和根據參數配置計算的結果一致。

concurrent mark-sweep generation:

capacity = 1342177280 (1280.0MB)

used = 763110504 (727.7588882446289MB)

free = 579066776 (552.2411117553711MB)

56.85616314411163% used

//永久代大小爲256M,實際使用不到50%。可在系統運行一段時間後穩定該值。

Perm Generation:

capacity = 268435456 (256.0MB)

used = 118994736 (113.48222351074219MB)

free = 149440720 (142.5177764892578MB)

44.32899355888367% used

  1. Top命令監控結果:

經過使用top命令進行持續監控發現此時CPU空閒比例爲85.7%,剩餘物理內存爲3619M,虛擬內存8G未使用。持續的監控結果顯示進程29003佔用系統內存不斷在增長,已經快獲得最大值。

  1. Jstat命令監控結果:

使用jstat命令對PID爲29003的進程進行gc回收狀況檢查,發現因爲Old段的內存使用量已經超過了設定的80%的警惕線,致使系統每隔一兩秒就進行一次FGC,FGC的次數明顯多餘YGC的次數,可是每次FGC後old的內存佔用比例卻沒有明顯變化—系統嘗試進行FGC也不能有效地回收這部分對象所佔內存。同時也說明年輕代的參數配置可能有問題,致使大部分對象都不得不放到老年代來進行FGC操做,這個或許跟系統配置的會話失效時間過長有關。

  1. Jstack打印出的堆棧內容:

在上圖中發現大量的的工做流線程鎖定。

在上圖中發現大量的的cms線程池管理線程鎖定。

  1. 緣由分析

經過對jvm內存進行實時監控後發現致使老年代內存不能有效回收的緣由就在於堆棧中存在大量的線程死鎖問題。建議開發組認真審查com.zzxy.workflow包的源代碼以及com.web.csm包中的源代碼,看看是否存在線程死鎖的缺陷。

  1. 該系統的JVM設置

<jvm-options>-XX:+PrintGCApplicationConcurrentTime</jvm-options> <jvm-options>-XX:+PrintGCApplicationStoppedTime</jvm-options>

<jvm-options>-XX:+PrintGCTimeStamps</jvm-options>

<jvm-options>-XX:+PrintGCDetails</jvm-options>

<jvm-options>-Xms2048m</jvm-options>

<jvm-options>-Xmx2048m</jvm-options>

<jvm-options>-server</jvm-options>

<jvm-options>-Djava.awt.headless=true</jvm-options>

<jvm-options>-XX:PermSize=256m</jvm-options>

<jvm-options>-XX:MaxPermSize=256m</jvm-options>

<jvm-options>-XX:+DisableExplicitGC</jvm-options>

<jvm-options>-Xmn768M</jvm-options>

<jvm-options>-XX:SurvivorRatio=3</jvm-options>

<jvm-options>-Xss128K</jvm-options>

<jvm-options>-XX:TargetSurvivorRatio=80</jvm-options>

<jvm-options>-XX:MaxTenuringThreshold=5</jvm-options>

<jvm-options>-XX:+UseConcMarkSweepGC</jvm-options>

<jvm-options>-XX:+CMSClassUnloadingEnabled</jvm-options>

<jvm-options>-XX:+UseCMSCompactAtFullCollection</jvm-options>

<jvm-options>-XX:-CMSParallelRemarkEnabled</jvm-options>

  1. 後記

一、性能調優要作到有的放矢,根據實際業務系統的特色,以必定時間的JVM日誌記錄爲依據,進行有針對性的調整、比較和觀察。

二、性能調優是個無止境的過程,要綜合權衡調優成本和更換硬件成本的大小,使用最經濟的手段達到最好的效果。

三、性能調優不只僅包括JVM的調優,還有服務器硬件配置、操做系統參數、中間件線程池、數據庫鏈接池、數據庫自己參數以及具體的數據庫表、索引、分區等的調整和優化。

四、經過特定工具檢查代碼中存在的性能問題並加以修正是一種比較經濟快捷的調優方法。

  1. 附:捨得網的典型配置

$JAVA_ARGS .= " -Dresin.home=$SERVER_ROOT

-server

-Xms6000M

-Xmx6000M

-Xmn500M

-XX:PermSize=500M

-XX:MaxPermSize=500M

-XX:SurvivorRatio=65536

-XX:MaxTenuringThreshold=0

-Xnoclassgc

-XX:+DisableExplicitGC

-XX:+UseParNewGC

-XX:+UseConcMarkSweepGC

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=0

-XX:+CMSClassUnloadingEnabled

-XX:-CMSParallelRemarkEnabled

-XX:CMSInitiatingOccupancyFraction=90

-XX:SoftRefLRUPolicyMSPerMB=0

-XX:+PrintClassHistogram

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-XX:+PrintHeapAtGC

-Xloggc:log/gc.log ";
說明:

1 -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0就是去掉了救助空間;

2-Xnoclassgc禁用類垃圾回收,性能會高一點;

3-XX:+DisableExplicitGC禁止System.gc(),省得程序員誤調用gc方法影響性能;

4-XX:+UseParNewGC,對年輕代採用多線程並行回收,這樣收得快;

相關文章
相關標籤/搜索