天天看點

一步步優化JVM四:決定Java堆的大小以及記憶體占用

到目前為止,還沒有做明确的優化工作。隻是做了初始化選擇工作,比如說:jvm部署模型、jvm運作環境、收集哪些垃圾回收器的資訊以及需要遵守垃圾回收原則。這一步将介紹如何評估應用需要的記憶體大小以及java堆大小。首先需要判斷出應用存活的資料的大小,存活資料的大小是決定配置應用需要的java堆大小的重要條件,也能夠決定是否需要重新審視一下應用的記憶體需求或者修改應用程式以滿足記憶體需求。

   注意:存活資料是指,應用處于穩定運作狀态下,在java堆裡面長期存活的對象。換一句話說,就是應用在穩定運作的狀态下,full gc之後,java堆的所占的空間。

限制

   有多少實體記憶體可以供jvm使用?是部署多個jvm或者單個jvm?對做出的決定有重要影響。下面列出了一些要點可以幫助決定有多少實體記憶體可以供使用。

   1、一個機器上面隻是部署一個jvm,且就一個應用使用?如果是這種情況,那麼機器的所有實體記憶體可以供jvm使用。

   2、一個機器上部署了多個jvm?或者一個機器上部署了多個應用?如果是這兩個中的任何一種情況,你就必須要決定每一個jvm或者應用需要配置設定多少記憶體了。

   無論是前面的哪種情況,都需要給作業系統留出一些記憶體。

hotspot vm的堆結構

   在做記憶體占用測量之前,我們必須要先了解hotspot vm java堆的結構,了解這個對決定應用需要的java堆大小以及優化垃圾收器性能有很好的幫助。

一步步優化JVM四:決定Java堆的大小以及記憶體占用

   hotspot vm有3個主要的空間:young代、old代以及permanent代,如上圖所示。

   當java應用配置設定java對象時,這些對象被配置設定到young代。在經曆過幾次minor gc之後,如果對象還是存活的,就會被轉移到old代空間。permanent代空間存儲了vm和java類的中繼資料比如内置的字元串和類的靜态變量。

 -xmx和-xms這個兩個指令行選項分别指定yound代加上old代空間的總和的初始最大值和最小值,也就是java堆的大小。當-xms的值小于-xmx的值的時候,java堆的大小可以在最大值和最小值之前浮動。當java應用強調吞吐量和延遲的時候,傾向于把-xms和-xmx設定成相同的值,由于調整young代或者old代的大小都需要進行full

gc,full gc降低吞吐量以及加強延遲。

   young代的空間可以通過下面的任意一個指令行選項來設定:

一步步優化JVM四:決定Java堆的大小以及記憶體占用

1、-xx:newsize=<n>[g|m|k]  

   young代的初始值和最小值。<n>是大小,[g|m|k]表示機關是g位元組,m位元組或者千位元組。young代的大小不會小于這個值。當設定-xx:newsize=<n>[g|m|k]的時候,-xx:maxnewsize=<n>[g|m|k]需要被指定。

一步步優化JVM四:決定Java堆的大小以及記憶體占用

2、-xx:maxnewsize=<n>[g|m|k]  

   young區空間的最大值。同上面反過來,當指定-xx:maxnewsize=<n>[g|m|k]的需要指定-xx:newsize=<n>[g|m|k]。

一步步優化JVM四:決定Java堆的大小以及記憶體占用

3、-xmn<n>[g|m|k]  

   直接指定young代的初始值、最小值以及最大值。也就是說,young區的大小被固定成這個值了。這個值用來鎖定young代的大小很友善。

   有一點需要注意的是,如果-xms和-xmx沒有被設定成相同的值,而且-xmn被使用了,當調整java堆的大小的時候,不會調整young代的空間大小,young代的空間大小會保持恒定。是以,-xmn應該在-xms和-xmx設定成相同的時候才指定。

 old代的空間大小可以基于young代的大小進行計算,old代的初始值的大小是-xms的值減去-xx:newsize,最大值是-xmx減去-xx:maxnewsize,如果-xmx和-xms設定成了相同的值,而且使用-xmn選項或者-xx:newsize和-xx:maxnewsize設定成了相同的值,那麼old代的大小就是-xmx減去-xmn。

   permanent代的大小通過下面指令行參數指定

一步步優化JVM四:決定Java堆的大小以及記憶體占用

1、-xx:permsize=<n>[g|m|k]  

   表示permanent代的初始值和最小值,n表示數值,g|m|k表示機關、permanent的空間一定不會比這個空間小。

一步步優化JVM四:決定Java堆的大小以及記憶體占用

2、-xx:maxpermsize=<n>[g|m|k]  

   permanent代的最大值,permanent代的大小不會超過這個值。

   java應用應該指定這兩個值成為同一個值,由于這個值的調整會導緻full gc。

   如果上面提到的java堆大小、young代、permanent代的大小都沒有指定,那麼jvm會根據應用的情況自行計算。

   在young代、old代以及permanent代中任何一個空間裡面無法配置設定對象的時候就會觸發垃圾回收,了解這點,對後面的優化非常重要。當young代沒有足夠空間配置設定java對象的時候,觸發minor gc。minor gc相對于full gc來說會更短暫。

 一個對象在經曆過一定次數的minor

gc之後,如果還存活,那麼會被轉移到old代(對象有一個“任期閥值”的概念,優化延遲的時候再介紹)。當old代沒有足夠空間放置對象的時候,hotspot

vm觸發full gc。實際上在進行minor

gc的時候發現沒有old代足夠的空間來進行對象的轉移,就會觸發fullgc,相對于在minorgc的過程中發現對象的移動失敗了然後觸發fullgc,這樣的政策會有更小的花費。當permanent代的空間不夠用的時候的,也會觸發fullgc。

   如果fullgc是由于old代滿了而觸發的,old代和permanent代的空間都會被垃圾回收,即使permanent代的空間還沒有滿。同理,如果fullgc是由于permanent代滿了而觸發的,old代和permanent代的空間都會被垃圾回收,即使old代的空間還沒有滿。另外,young代同樣會被垃圾回收,除非-xx:+scavengebeforefullgc選項被指定了,-xx:+scavengebeforefullgc關閉fullgc的時候young代的垃圾回收。

堆大小優化的起點

   為了進行java堆大小的優化,一個合适的起點很重要。這節描述的方案是需要先使用比應用需要量更大的java堆作為開始。這一步的目的是收集一些初始化資訊以及為了進一步優化java堆大小需要的資料。

 就像在“選擇jvm runtime”小節裡面提到過的,由吞吐量垃圾回收器(throughput garbage

collector)開始。記住,使用吞吐量垃圾回收器通過設定-xx:+userparalleloldgc指令行選項,如果你使用的hotspot

vm不支援的這個選項,那麼就使用-xx:+userparallelgc。

   如果你能夠準确的預估到應用需要消耗的java堆空間,可以通過設定-xmx和-xms來作為這個步驟的起點。如果你不知道該設定什麼值,就讓jvm來選擇吧,反正後面,都會根據實際情況進行優化調整。

   關于如何監控gc日志前面的“gc優化基礎”已經描述過了。gc日志會展示在使用中的java堆的大小。初始化和最大的堆大小可以通過-xx:+printcommandlineflags來檢視。-xx:+printcommandlineflags列印出在hotspot

vm初始化的時候選擇的初始值和最大值比如-xx:initialheapsize=<n>

-xx:maxheapsize=<m>,這裡n表示初始化的java堆大小值,m表示java堆的最大值。

   不管你是指定java堆的大小還是使用預設的大小,必須讓應用進入穩定運作的狀态,你必須要有能力和手段讓應用處于和線上穩定運作的狀态相同的狀态。

   如果在企圖讓應用進入穩定狀态的時候,你在垃圾回收日志裡面觀察到outofmemoryerror,注意是old代溢出還是permanent代溢出。下面一個old代溢出的例子:

一步步優化JVM四:決定Java堆的大小以及記憶體占用

2012-07-15t18: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代的統計資訊标示為paroldgen。這行表示了old代的在fullgc的時候占用的空間。從這個結果來看,可以得出的結論是old代的空間太小了,由于fullgc前後old代的被占用的空間和配置設定的空間基本相等了,是以,jvm報了outofmemoryerror。相比較,通過pspermgen這行可以看出permanent代的空間占用是32390k,和他的容量(65536k)比還是有一定的距離。

下面的例子展示了由于permanent太少了而導緻的outofmemoryerror發生的例子

一步步優化JVM四:決定Java堆的大小以及記憶體占用

2012-07-15t18:26:37.755-0600: [full gc  

  [psyounggen: 0k->0k(141632k)]  

  [paroldgen: 132538k->132538k(350208k)]  

  32538k->32538k(491840k)  

  [pspermgen: 65536k->65536k(65536k)],  

  0.2430136 secs]  

  [times: user=0.37 sys=0.00, real=0.24 secs]  

  java.lang.outofmemoryerror: permgen space  

 同上面一樣,把關鍵行标示出來了,通過pspermgen這行可以看出在fullgc前後,他的空間占用量都和他的容量相同,可以得出的結論是permanent代的空間條小了,這樣就導緻了outofmemoryerror。在這個例子裡面,old的占用空間(132538k)遠比他的容量(350208k)小。

 如果在垃圾回收日志中觀察到outofmemoryerror,嘗試把java堆的大小擴大到實體記憶體的80%~90%。尤其需要注意的是堆空間導緻的outofmemoryerror以及一定要增加空間。比如說,增加-xms和-xmx的值來解決old代的outofmemoryerror,增加-xx:permsize和-xx:maxpermsize來解決permanent代引起的outofmemoryerror。記住一點java堆能夠使用的容量受限于硬體以及是否使用64位的jvm。在擴大了java堆的大小之後,再檢查垃圾回收日志,直到沒有outofmemoryerror為止。

   如果應用運作在穩定狀态下沒有outofmemoryerror就可以進入下一步了,計算活動對象的大小。

計算活動對象的大小

   就像前面提到的,活動對象的大小是應用處于穩定運作狀态時,長時間存活資料占用的java堆的空間大小。換句話說,就是應用穩定運作是,在fullgc之後,old代和permanent代的空間大小。

   活動對象的大小可以通過垃圾回收日志檢視,它提供了一些優化資訊,如下:

   1、應用處于穩定運作狀态下,old代的java堆空間占用數量。

   2、應用處于穩定運作狀态下,permanent代的java堆空間占用數量。

   為了保證能夠準确的評估應用的活動對象大小,最好的做法是多看幾次fullgc之後java堆空間的大小,保證fullgc是發生在應用處于穩定運作的狀态。

 如果應用沒有發生fullgc或者發生fullgc的次數很少,在性能測試環境,可以通過java監控工具來觸發fullgc,比如使用visualvm和jconsole,這些工具在最新的jdk的bin目錄下可以找到,visualvm內建了jconsole,visualvm或者jconsole上面有一個觸發gc的按鈕。

 另外,jmap指令可以選擇來強制hotspot vm進行fullgc。jmap

需要-histo:live指令選項以及jvm程序id。jvm的程序id可以通過jps指令擷取。比如jvm的程序id是348,jmap指令用來觸發fullgc可以想如下這樣寫:

一步步優化JVM四:決定Java堆的大小以及記憶體占用

$ jmap -histo:live 348  

   jmap不僅僅觸發fullgc,而且産生堆的關于對象配置設定的概要資訊。不過就目前這步的目的而言,可以忽略産生的堆概要資訊。

初始化堆大小配置

   本節描述了怎樣利用活動對象的大小來決定初始化的java堆的大小。下面的圖,給出了應用存活的對象的大小。比較明智的做法是多收集幾次fullgc資訊,有更多的資訊,能夠做出更加好的決定。

一步步優化JVM四:決定Java堆的大小以及記憶體占用

   通過活動對象大小的資訊,可以做出關于java堆的大小有根據的決定,以及可以估計出最壞情況下會導緻的延遲。

   比較正常是,java堆大小的初始化值和最大值(通過-xms和-xmx選項來指定)應該是old代活動對象的大小的3到4倍。

 在上圖中顯示的fullgc資訊中,在fullgc之後old代的大小是295111k,差不多是295m,即活動的對象的大小是295m。是以,推薦的java堆的初始化和最大值應該是885m到1180m,即可以設定為-xms885m

-xmx1180m。在這個例子中,java堆的大小是1048570k差不多1048m,在推薦值範圍内。

 另外一個正常是,permanent的初始值和最大值(-xx:permsize和-xx:maxpermsize)應該permanent代活動對象大小的1.2到1.5倍。在上圖中看到在fullgc之後permanent代占用空間是32390k,差不多32m。是以,permanent代的推薦大小是38m到48m,即可以設定為-xx:permsize=48m

-xx:maxpermsize=48m(1.5倍)。這個例子裡面,permanent代的空間大小是65536k即64m,大出了17m,不過在1g記憶體的系統的中,這個數值完全可以忍受。

   另外一個正常是,young代空間應該是old代活動對象大小的1到1.5倍。那麼在這裡例子中,young代的大小可以設定為295m到442m。本例裡面,young代的空間大小的358400k,差不多358m,在推薦值中間。

   如果推薦的java堆的初始值和最大值是活動對象大小3到4倍,而young代的推薦隻是1到1.5倍,那麼old代空間大小應該是2到3倍。

   通過以上規則,我們可以使用的java指令可以是這樣的:

一步步優化JVM四:決定Java堆的大小以及記憶體占用

<span style="margin: 0px; padding: 0px; font-size: 14px;">  java -xms1180m -xmx1180m -xmn295m -xx:permsize=48m -xx:maxpermsize=48m</span>  

另外一些考慮

 本節将提及到在進行應用記憶體占用評估的時候,另外一些需要記住的點。首先,必須要知道,前面隻是評估的java堆的大小,而不是java應用占用的所有的記憶體,如果要檢視java應用占用的所有記憶體在linux下可以通過top指令檢視或者在window下面通過任務管理器來檢視,盡管java堆的大小可能對java應用占用記憶體做出了最大的貢獻。

比如說,為了存儲線程堆棧,應用需要額外的記憶體,越多的線程,越多記憶體被線程棧消耗,越深的方法間調用,線程棧越多。另外,本地庫需要配置設定額外的記憶體,i/o緩存也需要額外的記憶體。應用的記憶體消耗需要評估到應用任何一個會消耗記憶體的地方。

   記住,這一步操作不一定能夠滿足應用記憶體消耗的需求,如果不能滿足,就回過頭來看需求是否合理或者修改應用程式。比較可行的一種辦法是修改應用程式減小對象的配置設定,進而減少記憶體的消耗。

   java堆的大小計算僅僅隻是開始,根據需求,在後面的優化步驟中可能會修改。

原文連結:[http://wely.iteye.com/blog/2339046]