1. 程式人生 > >《java performance》翻譯 第七章jvm調優:堆記憶體設定

《java performance》翻譯 第七章jvm調優:堆記憶體設定

    設定jvm堆記憶體
    到目前為止,還沒有為調優jvm的記憶體佔用採取任何調優動作。下面這個步驟講述瞭如何來確定一個應用應該使用的jvm記憶體大小。這個步驟的目標是幫助調優人員找出應用的常駐記憶體大小,因為應用的常駐記憶體大小為配置應用的堆記憶體提供了很好的參考作用。常駐記憶體大小是指應用在穩定執行狀態需要使用的堆大小,另外一個角度來理解的話,可以認為是jvm在經歷過一次fgc之後的記憶體佔用大小。     首先要確定在你的部署機器上能夠給需要調優的jvm程序分配多大的記憶體空間,排除其他程序的記憶體消耗後,分配記憶體的原則是:如果你只在機器上部署一個jvm程序,那麼機器上所有剩餘的記憶體空間都可以分配給這個jvm程序。如果要部署多個jvm程序,那麼就要合理分配每個jvm上的記憶體。     在採取記憶體分配調優策略之前,需要先了解一下jvm的堆記憶體佈局,以便更好得在記憶體分配調優時採取措施。     Hotspot堆記憶體佈局引數
         hotspot 虛擬機器有三個主要的記憶體區域:年輕代、年老代和永久代。在hotspot虛擬機器中,新建立的物件被分配到年輕代中,經過多次minor gc依然存活的物件被晉升到年老代中。而永久代中則用來放置java class的元資料、intern的string以及類靜態變數等。     -Xms引數和-Xmx引數分別用來設定年輕代加上年老代這兩塊堆記憶體的最小值和最大值。-Xms標識了堆記憶體的初始值和最小值,而-Xmx標識了最大值。當應用的可用記憶體數減少時,jvm會在-Xmx引數指定的大小範圍內對對記憶體進行調整。一般情況下,建議把-Xms的值和-Xmx的值設成一樣的值,因為堆記憶體大小的調整需要進行fgc,而fgc會對應用的吞吐量和響應時間都產生影響。     年輕代大小的指定可以通過下列任一個引數進行:     -XX:NewSize=<n> g|m|k設定了年輕代的初始和最小值。     -XX:MaxNewSize=<n> g|m|k設定了年輕代的最大值。     -Xmn引數設定了年輕代的初始、最小和最大值。這個引數設定時,年輕代的最小值和最大值被設定成同樣大小。     年老代的值則由堆記憶體的總設定大小和年輕代設定的大小決定,年老代的最小值為-Xms設定值減去-XX:NewSize設定值,年老代最大值為-Xmx設定值減去-XX:MaxNewSize設定值。     永久代的大小由如下引數設定:     -XX:PermSize=<n> g|m|k設定了永久代的初始和最小值。     -XX:MaxPermSize=<n> g|m|k則設定了永久代的最大值。     一般情況推薦把-XX:PermSize設定成和-XX:MaxPermSize的值為相同的值,因為永久代大小的調整也會導致堆記憶體需要觸發fgc。     如果沒有設定這些java 堆記憶體分配的引數,jvm會根據系統的配置來確定預設的jvm堆記憶體設定引數。     當堆記憶體中任意一塊的記憶體區域不足,而jvm又需要在這一塊記憶體上分配記憶體時,垃圾回收就執行了。當年輕代記憶體不足時,jvm會觸發minor gc。minor gc是一種比fgc耗時更短的垃圾回收方式。經過幾次minor gc,如果物件還存活著,會被晉升到年老代中。隨著晉升物件的增加,當年老代中記憶體也不足時,fgc就發生了。實際上,jvm內部還會有特定的演算法來判斷“年老代中的物件不足以存放下一次minor gc晉升的物件”,根據這個判斷觸發fgc。此外,當永久代中的記憶體不足以jvm來分配java的型別元資料時,也會發生fgc。      當因為年老代不足發生fgc時,即使永久代中的記憶體並沒有滿,永久代和年老代中的資料都會被回收。如果因為永久代不足觸發fgc,年老代和永久代都會被垃圾回收。此外,如果沒有在jvm引數中設定-XX:ScavengeBeforeFullGC引數,預設的fgc會先觸發minor gc,-XX:ScavengeBeforeFullGC設定了在fgc之前不進行minor gc。     設定堆初始值     要開始調優jvm的堆記憶體大小,首先要設定一個jvm的初始記憶體大小。這一節開始會以比應用所需要的記憶體大得多的記憶體開始執行應用,這麼做的目的是通過這個步驟收集一些應用正常執行的初始化資料,然後再進行進一步的記憶體調優。     首先,使用-XX:UseParallelGC設定jvm的gc方式為並行GC。然後,如果你對應用需要的記憶體數量有一個大概的估計的話,你可以設定-Xms和-Xmx到你估計的這個值,如果你對應用需要的記憶體沒有概念,直接不指定jvm的堆大小,即讓jvm使用自己的初始化配置,這個配置和當前機器的配置有關。因為,這個步驟只是記憶體調優的初始步驟,我們隨後將對應用的堆記憶體進行重新設定。      接著,給你的應用以適當的請求壓力,這個壓力的大小和請求的內容應該是你期望應用正常服務情況下需要接收的請求大小和內容。這個狀態叫做應用的穩定狀態。     如果在你的應用日誌中觀察到OutOfMemoryError,那你首先要確認是由於年老代不足還是永久代不足導致的。     
    如果在gc日誌中觀察到了如上圖所示的gc日誌,可以判斷是年老代不足導致的gc,因為從圖中可知fgc前年老代的記憶體已經接近年老代的容量,而且FGC之後年老代記憶體的佔有量也沒有減少,而永久代還沒有達到容量的一半。     而如果在gc日誌中觀察到如下圖所示的日誌,則表明是由於永久代記憶體不足導致了fgc:
    因為從截圖中可以看到,在做fgc時,永久代已經使用了65536k,佔用率已經達到了100%。而年老代的佔用量132538k則遠沒有達到年老代的容量350208k。     如果在gc日誌中觀察到了OutOfMemoryError錯誤,下一步可以把java堆的記憶體調大,最多可以使用空閒記憶體的80%-90%,也要注意調整的jvm堆區塊,如果是年老代導致的OOM,則調整-Xms和-Xmx的值,如果是永久代導致的OOM,則調整-XX:PermSize和-XX:MaxPermSize的值。重複調整堆記憶體的大小,壓測你的應用到穩定服務狀態,然後觀察應用的gc日誌,直到應用沒有OutOfMemoryError。下一步就可以開始計算應用的常駐記憶體大小了。      JVM常駐記憶體大小
     JVM的常駐記憶體大小是指當應用在穩定提供服務狀態下存活的記憶體物件的總大小。換句話來理解,常駐記憶體大小可以被認為是在應用穩定得提供期望提供的服務狀態下進行一次fgc後年老代和年輕代佔的總大小。所以常駐記憶體大小給應用提供的兩個調優資訊是:      1.當應用在穩定提供期望服務狀態下需要的年老代大小。      2.當應用在穩定提供期望服務狀態下需要的永久代大小。      此外,在穩定提供期望服務狀態下進行fgc還可以得到這個狀態下發生fgc時對應用響應延遲的影響。      要得到JVM的常駐記憶體大小可以通過多次觀察應用在提供期望服務時發生fgc之後的堆記憶體情況來得到,如果應用需要花費很長時間才發生一次fgc,那則可以通過jvm的一些監控工具如VisualVM和jconsole來觸發fgc,這些工具提供了手工觸發jvm進行fgc的功能。使用jmap -histo:live <pid>也可以觸發一次fgc,這個命令會觸發jvm的一次fgc並收集jvm中存活物件的記憶體分配情況。      設定jvm堆記憶體      接下來一小節介紹如何根據jvm的常駐記憶體大小來設定初始化的jvm記憶體。下圖介紹瞭如何從fgc中獲取jvm的常駐記憶體資訊:           按照通常的經驗來講,jvm堆記憶體配置的值,即-Xms和-Xmx的值應該是jvm中年老代常駐記憶體大小的3至4倍。而永久代的大小(-XX:PermSzie和-XX:MaxPermSize)通常應用設定為永久代常駐記憶體的1.2到1.5倍。年輕代的大小(-Xmn)應該設定為年老代常駐記憶體大小的1至1.5倍。       其他部分記憶體設定       對於java應用,除了jvm堆記憶體的大小需要設定之外,還要考慮java應用中其他部分的記憶體消耗,比如執行緒棧中的記憶體分配,應用中的執行緒數越多,執行緒棧消耗的記憶體越大,執行緒的呼叫層次越深,執行緒棧需要使用到的記憶體也越多。此外,例如應用中使用了IO快取時,本地方法也會消耗記憶體。       當上述的設定不能滿足應用的記憶體使用期望時,可能需要重新調整應用記憶體的分配,另外一點,也需要對應用本身的記憶體使用進行優化,最根本的辦法就是減少應用中的物件分配和物件存活量。       響應時間調優        這個步驟的調優目的是調優jvm使得應用的響應時間滿足需求。這個步驟可能會需要不斷得調整jvm的堆記憶體大小並採集垃圾回收耗時和頻率,也可能會調整垃圾回收器,最終確定各區的記憶體佔用大小和垃圾回收器使用。        經過這步驟的調優,可能會得到下面兩個結果:        1.應用響應時間的滿足需求。如果這階段的調優動作確實優化了應用的響應時間並滿足了應用響應上的需求,則你可以繼續下一步開始應用吞吐量的調優了。        2.應用響應時間不滿足需求。如果這階段的調優動作完成後始終沒有達到應用的響應時間需求,那麼你需要做的可能是重新審視你對應用的響應時間需求,或者,在應用層面上進行優化,使之達到要求。你可能可以通常這兩個思路來優化應用的響應時間:一是減少應用的物件分配和常駐記憶體數,二是改變jvm的部署模型,增加jvm例項或者按功能拆分應用並部分到不同jvm上。