(轉)一步步優化JVM4:決定Java堆的大小以及內存佔用

一步步優化JVM4:決定Java堆的大小以及內存佔用

http://www.myexception.cn/program/564386.html

一步步優化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堆大小以及優化垃圾收器性能有很好的幫助。
   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代的空間可以通過下面的任意一個命令行選項來設置:

   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] 需要被指定。
   2、-XX:MaxNewSize=<n>[g|m|k]

   young區空間的最大值。同上面反過來,當指定 -XX:MaxNewSize =<n>[g|m|k] 的需要指定 -XX:NewSize=<n>[g|m|k]。
   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代的大小通過下面命令行參數指定
   1、-XX:PermSize=<n>[g|m|k]

   表示permanent代的初始值和最小值,n表示數值,g|m|k表示單位、permanent的空間一定不會比這個空間小。
    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代溢出的例子:

    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發生的例子

 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可以想如下這樣寫:
  $ 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命令可以是這樣的:
  java -Xms1180m -Xmx1180m -Xmn295m -XX:PermSize=48m -XX:MaxPermSize=48m

另外一些考慮
   本節將提及到在進行應用內存佔用評估的時候,另外一些需要記住的點。首先,必須要知道,前面只是評估的Java堆的大小,而不是Java應用佔用的所有 的內存,如果要查看Java應用佔用的所有內存在linux下可以通過top命令查看或者在window下面通過任務管理器來查看,儘管Java堆的大小 可能對Java應用佔用內存做出了最大的貢獻。 比如說,爲了存儲線程堆棧,應用需要額外的內存,越多的線程,越多內存被線程棧消耗,越深的方法間調用,線程棧越多。另外,本地庫需要分配額外的內 存,I/O緩存也需要額外的內存。應用的內存消耗需要評估到應用 任何一個會 消耗內存的地方。

   記住,這一步操作不一定能夠滿足應用內存消耗的需求,如果不能滿足,就回過頭來看需求是否合理或者修改應用程序。比較可行的一種辦法是修改應用程序減小對象的分配,從而減少內存的消耗。
   Java堆的大小計算僅僅只是開始,根據需求,在後面的優化步驟中可能會修改。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章