標籤:
原文:http://blog.csdn.net/zhoutao198712/article/details/7783070
到目前為止,還沒有做明確的最佳化工作。只是做了初始化選擇工作,比如說:JVM部署模型、JVM運行環境、收集哪些記憶體回收行程的資訊以及需要遵守記憶體回收原則。這一步將介紹如何評估應用需要的記憶體大小以及Java堆大小。首先需要判斷出應用存活的資料的大小,存活資料的大小是決定配置應用需要的Java堆大小的重要條件,也能夠決定是否需要重新審視一下應用的記憶體需求或者修改應用程式以滿足記憶體需求。
注意:存活資料是指,應用處於穩定運行狀態下,在Java堆裡面長期存活的對象。換一句話說,就是應用在穩定啟動並執行狀態下,Full GC之後,Java堆的所佔的空間。
約束 有多少實體記憶體可以供JVM使用?是部署多個JVM或者單個JVM?對做出的決定有重要影響。下面列出了一些要點可以協助決定有多少實體記憶體可以供使用。 1、一個機器上面只是部署一個JVM,且就一個應用使用?如果是這種情況,那麼機器的所有實體記憶體可以供JVM使用。 2、一個機器上部署了多個JVM?或者一個機器上部署了多個應用?如果是這兩個中的任何一種情況,你就必須要決定每一個JVM或者應用需要分配多少記憶體了。 無論是前面的哪種情況,都需要給作業系統留出一些記憶體。
HotSpot VM的堆結構 在做記憶體佔用測量之前,我們必須要先理解HotSpot VM Java堆的結構,理解這個對決定應用需要的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代的空間可以通過下面的任意一個命令列選項來設定:
[html] view plaincopy
- 1、-XX:NewSize=<n>[g|m|k]
young代的初始值和最小值。<n>是大小,[g|m|k]表示單位是G位元組,M位元組或者KB。young代的大小不會小於這個值。當設定-XX:NewSize=<n>[g|m|k]的時候,-XX:MaxNewSize=<n>[g|m|k]需要被指定。
[html] view plaincopy
- 2、-XX:MaxNewSize=<n>[g|m|k]
young區空間的最大值。同上面反過來,當指定-XX:MaxNewSize=<n>[g|m|k]的需要指定-XX:NewSize=<n>[g|m|k]。
[html] view plaincopy
- 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代的大小通過下面命令列參數指定
[html] view plaincopy
- 1、-XX:PermSize=<n>[g|m|k]
表示permanent代的初始值和最小值,n表示數值,g|m|k表示單位、permanent的空間一定不會比這個空間小。
[html] view plaincopy
- 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代溢出的例子:
[html] view plaincopy
- 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發生的例子
[html] view plaincopy
- 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可以想如下這樣寫:
[html] view plaincopy
- $ jmap -histo:live 348
jmap不僅僅觸發FullGC,而且產生堆的關於對象分配的概要資訊。不過就目前這步的目的而言,可以忽略產生的堆概要資訊。
初始化堆大小配置 本節描述了怎樣利用使用中的物件的大小來決定初始化的Java堆的大小。下面的圖,給出了應用存活的對象的大小。比較明智的做法是多收集幾次FullGC資訊,有更多的資訊,能夠做出更加好的決定。 通過使用中的物件大小的資訊,可以做出關於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命令可以是這樣的:
[html] view plaincopy
- <span style="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堆的大小計算僅僅只是開始,根據需求,在後面的最佳化步驟中可能會修改。
一步步最佳化JVM四:決定Java堆的大小以及記憶體佔用