1. 程式人生 > >Java系列筆記(4)

Java系列筆記(4)

目錄

引數設定
收集器搭配
啟動記憶體分配
監控工具和方法
調優方法
調優例項
     
光說不練假把式,學習Java GC機制的目的是為了實用,也就是為了在JVM出現問題時分析原因並解決之。通過學習,我覺得JVM監控與調優主要的著眼點在於如何配置、如何監控、如何優化3點上。下面就將針對這3點進行學習。
     (如果您對Java的記憶體區域劃分和記憶體回收機制尚不明確,那在閱讀本文前,請先閱讀我的前一篇部落格《Java系列筆記(3) - Java 記憶體區域和GC機制》,在該部落格中,詳細敘述了Java HotSpot虛擬機器(Sun/Oracle JDK系列預設的虛擬機器)的記憶體分配和垃圾回收機制。本文很多內容將依據上一篇部落格,同時,本文所針對的虛擬機器,也是HotSpot虛擬機器。)


引數設定

 在Java虛擬機器的引數中,有3種表示方法(出自:http://www.cnblogs.com/wenfeng762/archive/2011/08/14/2137810.html),用“ps -ef |grep "java"命令,可以得到當前Java程序的所有啟動引數和配置引數

  • 標準引數(-),所有的JVM實現都必須實現這些引數的功能,而且向後相容;
  • 非標準引數(-X),預設jvm實現這些引數的功能,但是並不保證所有jvm實現都滿足,且不保證向後相容;
  • 非Stable引數(-XX),此類引數各個jvm實現會有所不同,將來可能會隨時取消,需要慎重使用(但是,這些引數往往是非常有用的);

(額外的,-DpropertyName=“value”的形式定義了一些全域性屬性值,下面有介紹。)
本文只重點介紹一些重要和常用的引數,如果想了解全部引數,可以參考下面的文章:

標準引數

其實標準引數是用過Java的人都最熟悉的,就是你在執行java命令時後面加上的引數,如java -version, java -jar 等,輸入命令java -help或java -?就能獲得當前機器所有java的標準引數列表。
-client
設定jvm使用client模式,這是一般在pc機器上使用的模式,啟動很快,但效能和記憶體管理效率並不高;多用於桌面應用;

-server


使用server模式,啟動速度雖然慢(比client模式慢10%左右),但是效能和記憶體管理效率很高,適用於伺服器,用於生成環境、開發環境或測試環境的服務端;
如果沒有指定-server或-client,JVM啟動的時候會自動檢測當前主機是否為伺服器,如果是就以server模式啟動,64位的JVM只有server模式,所以無法使用-client引數;
預設情況下,不同的啟動模式,執行GC的方式有所區別:

啟動模式 新生代GC方式 舊生代和持久代GC的方式
client 序列 序列
server 並行 併發

如果沒有指定-server或-client模式,則判斷方法如下:

-classpath / -cp
JVM載入和搜尋檔案的目錄路徑,多個路徑用;分隔。注意,如果使用了-classpath,JVM就不會再搜尋環境變數中定義的CLASSPATH路徑。
JVM搜尋路徑的順序為:
1,先搜尋JVM自帶的jar或zip包(Bootstrat,搜尋路徑可以用System.getProperty("sun.boot.class.path")獲得);
2,搜尋JRE_HOME/lib/ext下的jar包(Extension,搜尋路徑可以用System.getProperty("java.ext.dirs")獲得);
3,搜尋使用者自定義目錄,順序為:當前目錄(.),CLASSPATH,-cp;(搜尋路徑用System.getProperty("java.class.path")獲得)

-DpropertyName=value
定義系統的全域性屬性值,如配置檔案地址等,如果value有空格,可以用-Dname="space string"這樣的形式來定義,用System.getProperty("propertyName")可以獲得這些定義的屬性值,在程式碼中也可以用System.setProperty("propertyName","value")的形式來定義屬性。

-verbose
這是查詢GC問題最常用的命令之一,具體引數如:
-verbose:class
 輸出jvm載入類的相關資訊,當jvm報告說找不到類或者類衝突時可此進行診斷。
-verbose:gc
 輸出每次GC的相關情況,後面會有更詳細的介紹。
-verbose:jni
 輸出native方法呼叫的相關情況,一般用於診斷jni呼叫錯誤資訊。

非標準引數
非標準引數,是在標準引數的基礎上進行擴充套件的引數,輸入“java -X”命令,能夠獲得當前JVM支援的所有非標準引數列表(你會發現,其實並不多哦)。

在不同型別的JVM中,採用的引數有所不同,
在講解非標準引數時,請參考下面的圖,對記憶體區域的大小有個形象的瞭解(下圖出自:http://iamzhongyong.iteye.com/blog/1333100):


-Xmn
新生代記憶體大小的最大值,包括E區和兩個S區的總和,使用方法如:-Xmn65535,-Xmn1024k,-Xmn512m,-Xmn1g (-Xms,-Xmx也是種寫法)
-Xmn只能使用在JDK1.4或之後的版本中,(之前的1.3/1.4版本中,可使用-XX:NewSize設定年輕代大小,用-XX:MaxNewSize設定年輕代最大值);
如果同時設定了-Xmn和-XX:NewSize,-XX:MaxNewSize,則誰設定在後面,誰就生效;如果同時設定了-XX:NewSize -XX:MaxNewSize與-XX:NewRatio則實際生效的值是:min(MaxNewSize,max(NewSize, heap/(NewRatio+1)))(看考:http://www.open-open.com/home/space.php?uid=71669&do=blog&id=8891)
在開發、測試環境,可以-XX:NewSize 和 -XX:MaxNewSize來設定新生代大小,但在線上生產環境,使用-Xmn一個即可(推薦),或者將-XX:NewSize 和 -XX:MaxNewSize設定為同一個值,這樣能夠防止在每次GC之後都要調整堆的大小(即:抖動,抖動會嚴重影響效能)

 -Xms
初始堆的大小,也是堆大小的最小值,預設值是總共的實體記憶體/64(且小於1G),預設情況下,當堆中可用記憶體小於40%(這個值可以用-XX: MinHeapFreeRatio 調整,如-X:MinHeapFreeRatio=30)時,堆記憶體會開始增加,一直增加到-Xmx的大小;

 -Xmx
堆的最大值,預設值是總共的實體記憶體/64(且小於1G),如果Xms和Xmx都不設定,則兩者大小會相同,預設情況下,當堆中可用記憶體大於70%(這個值可以用-XX: MaxHeapFreeRatio 調整,如-X:MaxHeapFreeRatio=60)時,堆記憶體會開始減少,一直減小到-Xms的大小;
整個堆的大小=年輕代大小+年老代大小,堆的大小不包含持久代大小,如果增大了年輕代,年老代相應就會減小,官方預設的配置為年老代大小/年輕代大小=2/1左右(使用-XX:NewRatio可以設定-XX:NewRatio=5,表示年老代/年輕代=5/1);
建議在開發測試環境可以用Xms和Xmx分別設定最小值最大值,但是在線上生產環境,Xms和Xmx設定的值必須一樣,原因與年輕代一樣——防止抖動;

 -Xss
這個引數用於設定每個執行緒的棧記憶體,預設1M,一般來說是不需要改的。除非程式碼不多,可以設定的小點,另外一個相似的引數是-XX:ThreadStackSize,這兩個引數在1.6以前,都是誰設定在後面,誰就生效;1.6版本以後,-Xss設定在後面,則以-Xss為準,-XXThreadStackSize設定在後面,則主執行緒以-Xss為準,其它執行緒以-XX:ThreadStackSize為準。

 -Xrs
減少JVM對作業系統訊號(OS Signals)的使用(JDK1.3.1之後才有效),當此引數被設定之後,jvm將不接收控制檯的控制handler,以防止與在後臺以服務形式執行的JVM衝突(這個用的比較少,參考:http://www.blogjava.net/midstr/archive/2008/09/21/230265.html)。

-Xprof
 跟蹤正執行的程式,並將跟蹤資料在標準輸出輸出;適合於開發環境除錯。

-Xnoclassgc
 關閉針對class的gc功能;因為其阻止記憶體回收,所以可能會導致OutOfMemoryError錯誤,慎用;

-Xincgc
 開啟增量gc(預設為關閉);這有助於減少長時間GC時應用程式出現的停頓;但由於可能和應用程式併發執行,所以會降低CPU對應用的處理能力。

-Xloggc:file
 與-verbose:gc功能類似,只是將每次GC事件的相關情況記錄到一個檔案中,檔案的位置最好在本地,以避免網路的潛在問題。
 若與verbose命令同時出現在命令列中,則以-Xloggc為準。

非Stable引數(非靜態引數)
以-XX表示的非Stable引數,雖然在官方文件中是不確定的,不健壯的,各個公司的實現也各有不同,但往往非常實用,所以這部分引數對於GC非常重要。JVM(Hotspot)中主要的引數可以大致分為3類(參考http://blog.csdn.net/sfdev/article/details/2063928):

  • 效能引數( Performance Options):用於JVM的效能調優和記憶體分配控制,如初始化記憶體大小的設定;
  • 行為引數(Behavioral Options):用於改變JVM的基礎行為,如GC的方式和演算法的選擇;
  • 除錯引數(Debugging Options):用於監控、列印、輸出等jvm引數,用於顯示jvm更加詳細的資訊;


對於非Stable引數,使用方法有4種:

  • -XX:+<option> 啟用選項
  • -XX:-<option> 不啟用選項
  • -XX:<option>=<number> 給選項設定一個數字型別值,可跟單位,例如 32k, 1024m, 2g
  • -XX:<option>=<string> 給選項設定一個字串值,例如-XX:HeapDumpPath=./dump.core

首先介紹效能引數,效能引數往往用來定義記憶體分配的大小和比例,相比於行為引數和除錯引數,一個比較明顯的區別是效能引數後面往往跟的有數值,常用如下:

引數及其預設值 描述
-XX:NewSize=2.125m 新生代物件生成時佔用記憶體的預設值
-XX:MaxNewSize=size 新生成物件能佔用記憶體的最大值
-XX:MaxPermSize=64m 方法區所能佔用的最大記憶體(非堆記憶體)
-XX:PermSize=64m 方法區分配的初始記憶體
-XX:MaxTenuringThreshold=15 物件在新生代存活區切換的次數(堅持過MinorGC的次數,每堅持過一次,該值就增加1),大於該值會進入老年代
-XX:MaxHeapFreeRatio=70 GC後java堆中空閒量佔的最大比例,大於該值,則堆記憶體會減少
-XX:MinHeapFreeRatio=40 GC後java堆中空閒量佔的最小比例,小於該值,則堆記憶體會增加
-XX:NewRatio=2 新生代記憶體容量與老生代記憶體容量的比例
-XX:ReservedCodeCacheSize= 32m 保留程式碼佔用的記憶體容量
-XX:ThreadStackSize=512 設定執行緒棧大小,若為0則使用系統預設值
-XX:LargePageSizeInBytes=4m 設定用於Java堆的大頁面尺寸
-XX:PretenureSizeThreshold= size    大於該值的物件直接晉升入老年代(這種物件少用為好)
-XX:SurvivorRatio=8 Eden區域Survivor區的容量比值,如預設值為8,代表Eden:Survivor1:Survivor2=8:1:1

常用的行為引數,主要用來選擇使用什麼樣的垃圾收集器組合,以及控制執行過程中的GC策略等:

引數及其預設值 描述
-XX:-UseSerialGC 啟用序列GC,即採用Serial+Serial Old模式
-XX:-UseParallelGC 啟用並行GC,即採用Parallel Scavenge+Serial Old收集器組合(-Server模式下的預設組合)
-XX:GCTimeRatio=99 設定使用者執行時間佔總時間的比例(預設值99,即1%的時間用於GC)
-XX:MaxGCPauseMillis=time 設定GC的最大停頓時間(這個引數只對Parallel Scavenge有效)
-XX:+UseParNewGC 使用ParNew+Serial Old收集器組合
-XX:ParallelGCThreads 設定執行記憶體回收的執行緒數,在+UseParNewGC的情況下使用
-XX:+UseParallelOldGC 使用Parallel Scavenge +Parallel Old組合收集器
-XX:+UseConcMarkSweepGC 使用ParNew+CMS+Serial Old組合併發收集,優先使用ParNew+CMS,當用戶執行緒記憶體不足時,採用備用方案Serial Old收集。
-XX:-DisableExplicitGC 禁止呼叫System.gc();但jvm的gc仍然有效
-XX:+ScavengeBeforeFullGC 新生代GC優先於Full GC執行

常用的除錯引數,主要用於監控和列印GC的資訊:

引數及其預設值 描述
-XX:-CITime 列印消耗在JIT編譯的時間
-XX:ErrorFile=./hs_err_pid<pid>.log 儲存錯誤日誌或者資料到檔案中
-XX:-ExtendedDTraceProbes 開啟solaris特有的dtrace探針
-XX:HeapDumpPath=./java_pid<pid>.hprof 指定匯出堆資訊時的路徑或檔名
-XX:-HeapDumpOnOutOfMemoryError 當首次遭遇OOM時匯出此時堆中相關資訊
-XX:OnError="<cmd args>;<cmd args>" 出現致命ERROR之後執行自定義命令
-XX:OnOutOfMemoryError="<cmd args>;<cmd args>" 當首次遭遇OOM時執行自定義命令
-XX:-PrintClassHistogram 遇到Ctrl-Break後列印類例項的柱狀資訊,與jmap -histo功能相同
-XX:-PrintConcurrentLocks 遇到Ctrl-Break後列印併發鎖的相關資訊,與jstack -l功能相同
-XX:-PrintCommandLineFlags 列印在命令列中出現過的標記
-XX:-PrintCompilation 當一個方法被編譯時列印相關資訊
-XX:-PrintGC 每次GC時列印相關資訊
-XX:-PrintGC Details 每次GC時列印詳細資訊
-XX:-PrintGCTimeStamps 列印每次GC的時間戳
-XX:-TraceClassLoading 跟蹤類的載入資訊
-XX:-TraceClassLoadingPreorder 跟蹤被引用到的所有類的載入資訊
-XX:-TraceClassResolution 跟蹤常量池
-XX:-TraceClassUnloading 跟蹤類的解除安裝資訊
-XX:-TraceLoaderConstraints 跟蹤類載入器約束的相關資訊

 再次宣告,上面的三種引數,主要參考了部落格:http://blog.csdn.net/sfdev/article/details/2063928和http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm,後一個比較全面,有興趣的可以仔細研讀。
這些引數將為我們進行GC的監控與調優提供很大助力,是我們進行GC相關操作的重要工具。
收集器搭配

在介紹了常用的配置引數之後,我們將開始真正的JVM實操征程,首先,我們要為應用程式選擇一個合適的垃圾收集器組合,本節請參考《Java系列筆記(3) - Java 記憶體區域和GC機制》一文中的“垃圾收集器”一節,及上節中的行為引數。

這裡需要再次引用這幅圖(圖來源於《深入理解Java虛擬機器:JVM高階特效與最佳實現》,圖中兩個收集器之間有連線,說明它們可以配合使用):


Serial收集器: Serial收集器是在client模式下預設的新生代收集器,其收集效率大約是100M左右的記憶體需要幾十到100多毫秒;在client模式下,收集桌面應用的記憶體垃圾,基本上不影響使用者體驗。所以,一般的Java桌面應用中,直接使用Serial收集器(不需要配置引數,用預設即可)。
ParNew收集器:Serial收集器的多執行緒版本,這種收集器預設開通的執行緒數與CPU數量相同,-XX:ParallelGCThreads可以用來設定開通的執行緒數。
可以與CMS收集器配合使用,事實上用-XX:+UseConcMarkSweepGC選擇使用CMS收集器時,預設使用的就是ParNew收集器,所以不需要額外設定-XX:+UseParNewGC,設定了也不會衝突,因為會將ParNew+Serial Old作為一個備選方案;
如果單獨使用-XX:+UseParNewGC引數,則選擇的是ParNew+Serial Old收集器組合收集器。
一般情況下,在server模式下,如果選擇CMS收集器,則優先選擇ParNew收集器。
Parallel Scavenge收集器:關注的是吞吐量(關於吞吐量的含義見上一篇部落格),可以這麼理解,關注吞吐量,意味著強調任務更快的完成,而如CMS等關注停頓時間短的收集器,強調的是使用者互動體驗。
在需要關注吞吐量的場合,比如資料運算伺服器等,就可以使用Parallel Scavenge收集器。

老年代收集器如下:
Serial Old收集器:在1.5版本及以前可以與 Parallel Scavenge結合使用(事實上,也是當時Parallel Scavenge唯一能用的版本),另外就是在使用CMS收集器時的備用方案,發生 Concurrent Mode Failure時使用。
如果是單獨使用,Serial Old一般用在client模式中。
Parallel Old收集器:在1.6版本之後,與 Parallel Scavenge結合使用,以更好的貫徹吞吐量優先的思想,如果是關注吞吐量的伺服器,建議使用Parallel Scavenge + Parallel Old 收集器。
CMS收集器:這是當前階段使用很廣的一種收集器,國內很多大的網際網路公司線上伺服器都使用這種垃圾收集器(http://blog.csdn.net/wisgood/article/details/17067203),筆者公司的收集器也是這種,CMS收集器以獲取最短回收停頓時間為目標,非常適合對使用者響應比較高的B/S架構伺服器。
 CMSIncrementalMode: CMS收集器變種,屬增量式垃圾收集器,在併發標記和併發清理時交替執行垃圾收集器和使用者執行緒。
 G1 收集器:面向伺服器端應用的垃圾收集器,計劃未來替代CMS收集器。

  • 一般來說,如果是Java桌面應用,建議採用Serial+Serial Old收集器組合,即:-XX:+UseSerialGC(-client下的預設引數)
  • 在開發/測試環境,可以採用預設引數,即採用Parallel Scavenge+Serial Old收集器組合,即:-XX:+UseParallelGC(-server下的預設引數)
  • 在線上運算優先的環境,建議採用Parallel Scavenge+Serial Old收集器組合,即:-XX:+UseParallelGC
  • 在線上服務響應優先的環境,建議採用ParNew+CMS+Serial Old收集器組合,即:-XX:+UseConcMarkSweepGC

另外在選擇了垃圾收集器組合之後,還要配置一些輔助引數,以保證收集器可以更好的工作。關於這些引數,請在http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm中查詢其意義和用法,如:

  • 選用了ParNew收集器,你可能需要配置4個引數: -XX:SurvivorRatio, -XX:PretenureSizeThreshold, -XX:+HandlePromotionFailure,-XX:MaxTenuringThreshold;
  • 選用了 Parallel Scavenge收集器,你可能需要配置3個引數: -XX:MaxGCPauseMillis,-XX:GCTimeRatio, -XX:+UseAdaptiveSizePolicy ;
  • 選用了CMS收集器,你可能需要配置3個引數: -XX:CMSInitiatingOccupancyFraction, -XX:+UseCMSCompactAtFullCollection, -XX:CMSFullGCsBeforeCompaction;

啟動記憶體分配

關於GC有一個常見的疑問是,在啟動時,我的記憶體如何分配?經過前面的學習,已經很容易知道,用-Xmn,-Xmx,-Xms,-Xss,-XX:NewSize,-XX:MaxNewSize,-XX:MaxPermSize,-XX:PermSize,-XX:SurvivorRatio,-XX:PretenureSizeThreshold,-XX:MaxTenuringThreshold就基本可以配置記憶體啟動時的分配情況。但是,具體配置多少?設定小了,頻繁GC(甚至記憶體溢位),設定大了,記憶體浪費。結合前面對於記憶體區域和其作用的學習,儘量考慮如下建議:

  1. -XX:PermSize儘量比-XX:MaxPermSize小,-XX:MaxPermSize>= 2 * -XX:PermSize, -XX:PermSize> 64m,一般對於4G記憶體的機器,-XX:MaxPermSize不會超過256m;
  2. -Xms =  -Xmx(線上Server模式),以防止抖動,大小受作業系統和記憶體大小限制,如果是32位系統,則一般-Xms設定為1g-2g(假設有4g記憶體),在64位系統上,沒有限制,不過一般為機器最大記憶體的一半左右;
  3. -Xmn,在開發環境下,可以用-XX:NewSize和-XX:MaxNewSize來設定新生代的大小(-XX:NewSize<=-XX:MaxNewSize),在生產環境,建議只設置-Xmn,一般-Xmn的大小是-Xms的1/2左右,不要設定的過大或過小,過大導致老年代變小,頻繁Full GC,過小導致minor GC頻繁。如果不設定-Xmn,可以採用-XX:NewRatio=2來設定,也是一樣的效果;
  4. -Xss一般是不需要改的,預設值即可。
  5. -XX:SurvivorRatio一般設定8-10左右,推薦設定為10,也即:Survivor區的大小是Eden區的1/10,一般來說,普通的Java程式應用,一次minorGC後,至少98%-99%的物件,都會消亡,所以,survivor區設定為Eden區的1/10左右,能使Survivor區容納下10-20次的minor GC才滿,然後再進入老年代,這個與 -XX:MaxTenuringThreshold的預設值15次也相匹配的。如果XX:SurvivorRatio設定的太小,會導致本來能通過minor回收掉的物件提前進入老年代,產生不必要的full gc;如果XX:SurvivorRatio設定的太大,會導致Eden區相應的被壓縮。
  6. -XX:MaxTenuringThreshold預設為15,也就是說,經過15次Survivor輪換(即15次minor GC),就進入老年代, 如果設定的小的話,則年輕代物件在survivor中存活的時間減小,提前進入年老代,對於年老代比較多的應用,可以提高效率。如果將此值設定為一個較大值,則年輕代物件會在Survivor區進行多次複製,這樣可以增加物件在年輕代的存活時間,增加在年輕代即被回收的概率。需要注意的是,設定了 -XX:MaxTenuringThreshold,並不代表著,物件一定在年輕代存活15次才被晉升進入老年代,它只是一個最大值,事實上,存在一個動態計算機制,計算每次晉入老年代的閾值,取閾值和MaxTenuringThreshold中較小的一個為準。
  7. -XX:PretenureSizeThreshold一般採用預設值即可。

監控工具和方法

在JVM執行的過程中,為保證其穩定、高效,或在出現GC問題時分析問題原因,我們需要對GC進行監控。所謂監控,其實就是分析清楚當前GC的情況。其目的是鑑別JVM是否在高效的進行垃圾回收,以及有沒有必要進行調優。
通過監控GC,我們可以搞清楚很多問題,如:
1,minor GC和full GC的頻率;
2,執行一次GC所消耗的時間;
3,新生代的物件何時被移到老生代以及花費了多少時間;
4,每次GC中,其它執行緒暫停(Stop the world)的時間;
5,每次GC的效果如何,是否不理想;
………………
監控GC的工具分為2種:命令列工具和圖形工具;
常用的命令列工具有:
注:下面的命令都在JAVA_HOME/bin中,是java自帶的命令。如果您發現無法使用,請直接進入Java安裝目錄呼叫或者先設定Java的環境變數,一個簡單的辦法為:直接執行命令 export PATH=$JAVA_HOME/bin:$PATH;另外,一般的,在Linux下,下面的命令需要sudo許可權,在windows下,部分命令的部分選項不能使用。
1,jps
jps命令用於查詢正在執行的JVM程序,常用的引數為:
    -q:只輸出LVMID,省略主類的名稱
    -m:輸出虛擬機器程序啟動時傳給主類main()函式的引數
    -l:輸出主類的全類名,如果程序執行的是Jar包,輸出Jar路徑
    -v:輸出虛擬機器程序啟動時JVM引數
命令格式:jps [option] [hostid]
一個簡單的例子:


在上圖中,有一個vid為309的apache程序在提供web服務。

2,jstat
jstat可以實時顯示本地或遠端JVM程序中類裝載、記憶體、垃圾收集、JIT編譯等資料(如果要顯示遠端JVM資訊,需要遠端主機開啟RMI支援)。如果在服務啟動時沒有指定啟動引數-verbose:gc,則可以用jstat實時檢視gc情況。
jstat有如下選項:
   -class:監視類裝載、解除安裝數量、總空間及類裝載所耗費的時間
   -gc:監聽Java堆狀況,包括Eden區、兩個Survivor區、老年代、永久代等的容量,以用空間、GC時間合計等資訊
   -gccapacity:監視內容與-gc基本相同,但輸出主要關注java堆各個區域使用到的最大和最小空間
   -gcutil:監視內容與-gc基本相同,但輸出主要關注已使用空間佔總空間的百分比
   -gccause:與-gcutil功能一樣,但是會額外輸出導致上一次GC產生的原因
   -gcnew:監視新生代GC狀況
   -gcnewcapacity:監視內同與-gcnew基本相同,輸出主要關注使用到的最大和最小空間
   -gcold:監視老年代GC情況
   -gcoldcapacity:監視內同與-gcold基本相同,輸出主要關注使用到的最大和最小空間
   -gcpermcapacity:輸出永久代使用到最大和最小空間
   -compiler:輸出JIT編譯器編譯過的方法、耗時等資訊
   -printcompilation:輸出已經被JIT編譯的方法
命令格式:jstat [option vmid [interval[s|ms] [count]]]
jstat可以監控遠端機器,命令格式中VMID和LVMID特別說明:如果是本地虛擬機器程序,VMID和LVMID是一致的,如果是遠端虛擬機器程序,那麼VMID格式是: [protocol:][//]lvmid[@hostname[:port]/servername],如果省略interval和count,則只查詢一次
檢視gc情況的例子:


在圖中,命令sudo jstat -gc 309 1000 5代表著:蒐集vid為309的java程序的整體gc狀態, 每1000ms收集一次,共收集5次;XXXC表示該區容量,XXXU表示該區使用量,各列解釋如下:
S0C:S0區容量(S1區相同,略)
S0U:S0區已使用
EC:E區容量
EU:E區已使用
OC:老年代容量
OU:老年代已使用
PC:Perm容量
PU:Perm區已使用
YGC:Young GC(Minor GC)次數
YGCT:Young GC總耗時
FGC:Full GC次數
FGCT:Full GC總耗時
GCT:GC總耗時

用gcutil檢視記憶體的例子:


圖中的各列與用gc引數時基本一致,不同的是這裡顯示的是已佔用的百分比,如S0為86.53,代表著S0區已使用了86.53%

3,jinfo
用於查詢當前執行這的JVM屬性和引數的值。
jinfo可以使用如下選項:
   -flag:顯示未被顯示指定的引數的系統預設值
   -flag [+|-]name或-flag name=value: 修改部分引數
   -sysprops:列印虛擬機器程序的System.getProperties()
 命令格式:jinfo [option] pid

4,jmap
用於顯示當前Java堆和永久代的詳細資訊(如當前使用的收集器,當前的空間使用率等)
   -dump:生成java堆轉儲快照
   -heap:顯示java堆詳細資訊(只在Linux/Solaris下有效)
   -F:當虛擬機器程序對-dump選項沒有響應時,可使用這個選項強制生成dump快照(只在Linux/Solaris下有效)
   -finalizerinfo:顯示在F-Queue中等待Finalizer執行緒執行finalize方法的物件(只在Linux/Solaris下有效)
   -histo:顯示堆中物件統計資訊
   -permstat:以ClassLoader為統計口徑顯示永久代記憶體狀態(只在Linux/Solaris下有效)
 命令格式:jmap [option] vmid
其中前面3個引數最重要,如:
檢視對詳細資訊:sudo jmap -heap 309
生成dump檔案: sudo jmap -dump:file=./test.prof 309
部分使用者沒有許可權時,採用admin使用者:sudo -u admin -H  jmap -dump:format=b,file=檔名.hprof pid
檢視當前堆中物件統計資訊:sudo  jmap -histo 309:該命令顯示3列,分別為物件數量,物件大小,物件名稱,通過該命令可以檢視是否記憶體中有大物件;
有的使用者可能沒有jmap許可權:sudo -u admin -H jmap -histo 309 | less

5,jhat
用於分析使用jmap生成的dump檔案,是JDK自帶的工具,使用方法為: jhat -J -Xmx512m [file]
不過jhat沒有mat好用,推薦使用mat(Eclipse外掛: http://www.eclipse.org/mat ),mat速度更快,而且是圖形介面。

6,jstack
用於生成當前JVM的所有執行緒快照,執行緒快照是虛擬機器每一條執行緒正在執行的方法,目的是定位執行緒出現長時間停頓的原因。
   -F:當正常輸出的請求不被響應時,強制輸出執行緒堆疊
   -l:除堆疊外,顯示關於鎖的附加資訊
   -m:如果呼叫到本地方法的話,可以顯示C/C++的堆疊
命令格式:jstack [option] vmid


7,-verbosegc
-verbosegc是一個比較重要的啟動引數,記錄每次gc的日誌,下面的表格對比了jstat和-verbosegc:


jstat
-verbosegc
監控物件 執行在本機的Java應用可以把日誌輸出到終端上,或者藉助jstatd命令通過網路連線遠端的Java應用。 只有那些把-verbogc作為啟動引數的JVM。
輸出資訊 堆狀態(已用空間,最大限制,GC執行次數/時間,等等) 執行GC前後新生代和老年代空間大小,GC執行時間。
輸出時間 Every designated time
每次設定好的時間。
每次GC發生的時候。
用途 觀察堆空間變化情況 瞭解單次GC產生的效果。


與-verbosegc配合使用的一些常用引數為:
   -XX:+PrintGCDetails,列印GC資訊,這是-verbosegc預設開啟的選項
   -XX:+PrintGCTimeStamps,列印每次GC的時間戳
   -XX:+PrintHeapAtGC:每次GC時,列印堆資訊
   -XX:+PrintGCDateStamps (from JDK 6 update 4) :列印GC日期,適合於長期執行的伺服器
   -Xloggc:/home/admin/logs/gc.log:制定列印資訊的記錄的日誌位置
每條verbosegc打印出的gc日誌,都類似於下面的格式:
time [GC [<collector>: <starting occupancy1> -> <ending occupancy1>(total occupancy1), <pause time1> secs] <starting occupancy3> -> <ending occupancy3>(total occupancy3), <pause time3> secs]
如:


這些選項的意義是:
time:執行GC的時間,需要新增-XX:+PrintGCDateStamps引數才有;
collector:minor gc使用的收集器的名字。
starting occupancy1:GC執行前新生代空間大小。
ending occupancy1:GC執行後新生代空間大小。
total occupancy1:新生代總大小
pause time1:因為執行minor GC,Java應用暫停的時間。
starting occupancy3:GC執行前堆區域總大小
ending occupancy3:GC執行後堆區域總大小
total occupancy3:堆區總大小
pause time3:Java應用由於執行堆空間GC(包括full GC)而停止的時間。

8,視覺化工具
監控和分析GC也有一些視覺化工具,比較常見的有JConsole和VisualVM,有興趣的可以看看下面的文章,在此不再贅述:
http://blog.csdn.net/java2000_wl/article/details/8049707

調優方法

一切都是為了這一步,調優,在調優之前,我們需要記住下面的原則:

  1. 多數的Java應用不需要在伺服器上進行GC優化;
  2. 多數導致GC問題的Java應用,都不是因為我們引數設定錯誤,而是程式碼問題;
  3. 在應用上線之前,先考慮將機器的JVM引數設定到最優(最適合);
  4. 減少建立物件的數量;
  5. 減少使用全域性變數和大物件;
  6. GC優化是到最後不得已才採用的手段;
  7. 在實際使用中,分析GC情況優化程式碼比優化GC引數要多得多;

GC優化的目的有兩個(http://www.360doc.com/content/13/0305/10/15643_269388816.shtml):

  • 將轉移到老年代的物件數量降低到最小;
  • 減少full GC的執行時間;

為了達到上面的目的,一般地,你需要做的事情有:

  • 減少使用全域性變數和大物件;
  • 調整新生代的大小到最合適;
  • 設定老年代的大小為最合適;
  • 選擇合適的GC收集器;

在上面的4條方法中,用了幾個“合適”,那究竟什麼才算合適,一般的,請參考上面“收集器搭配”和“啟動記憶體分配”兩節中的建議。但這些建議不是萬能的,需要根據您的機器和應用情況進行發展和變化,實際操作中,可以將兩臺機器分別設定成不同的GC引數,並且進行對比,選用那些確實提高了效能或減少了GC時間的引數。

真正熟練的使用GC調優,是建立在多次進行GC監控和調優的實戰經驗上的,進行監控和調優的一般步驟為:
1,監控GC的狀態
使用各種JVM工具,檢視當前日誌,分析當前JVM引數設定,並且分析當前堆記憶體快照和gc日誌,根據實際的各區域記憶體劃分和GC執行時間,覺得是否進行優化;
2,分析結果,判斷是否需要優化
如果各項引數設定合理,系統沒有超時日誌出現,GC頻率不高,GC耗時不高,那麼沒有必要進行GC優化;如果GC時間超過1-3秒,或者頻繁GC,則必須優化;
注:如果滿足下面的指標,則一般不需要進行GC:

  • Minor GC執行時間不到50ms;
  • Minor GC執行不頻繁,約10秒一次;
  • Full GC執行時間不到1s;
  • Full GC執行頻率不算頻繁,不低於10分鐘1次;

3,調整GC型別和記憶體分配
如果記憶體分配過大或過小,或者採用的GC收集器比較慢,則應該優先調整這些引數,並且先找1臺或幾臺機器進行beta,然後比較優化過的機器和沒有優化的機器的效能對比,並有針對性的做出最後選擇;
4,不斷的分析和調整
通過不斷的試驗和試錯,分析並找到最合適的引數
5,全面應用引數
如果找到了最合適的引數,則將這些引數應用到所有伺服器,並進行後續跟蹤。

調優例項

上面的內容都是紙上談兵,下面我們以一些真例項子來進行說明:
例項1:
筆者昨日發現部分開發測試機器出現異常:java.lang.OutOfMemoryError: GC overhead limit exceeded,這個異常代表:GC為了釋放很小的空間卻耗費了太多的時間,其原因一般有兩個:1,堆太小,2,有死迴圈或大物件;
筆者首先排除了第2個原因,因為這個應用同時是在線上執行的,如果有問題,早就掛了。所以懷疑是這臺機器中堆設定太小;
使用ps -ef |grep "java"檢視,發現:


該應用的堆區設定只有768m,而機器記憶體有2g,機器上只跑這一個java應用,沒有其他需要佔用記憶體的地方。另外,這個應用比較大,需要佔用的記憶體也比較多;
筆者通過上面的情況判斷,只需要改變堆中各區域的大小設定即可,於是改成下面的情況:


跟蹤執行情況發現,相關異常沒有再出現;

例項2:(http://www.360doc.com/content/13/0305/10/15643_269388816.shtml)
一個服務系統,經常出現卡頓,分析原因,發現Full GC時間太長:
jstat -gcutil:
S0     S1    E     O       P        YGC YGCT FGC FGCT  GCT
12.16 0.00 5.18 63.78 20.32  54   2.047 5     6.946  8.993
分析上面的資料,發現Young GC執行了54次,耗時2.047秒,每次Young GC耗時37ms,在正常範圍,而Full GC執行了5次,耗時6.946秒,每次平均1.389s,資料顯示出來的問題是:Full GC耗時較長,分析該系統的是指發現,NewRatio=9,也就是說,新生代和老生代大小之比為1:9,這就是問題的原因:
1,新生代太小,導致物件提前進入老年代,觸發老年代發生Full GC;
2,老年代較大,進行Full GC時耗時較大;
優化的方法是調整NewRatio的值,調整到4,發現Full GC沒有再發生,只有Young GC在執行。這就是把物件控制在新生代就清理掉,沒有進入老年代(這種做法對一些應用是很有用的,但並不是對所有應用都要這麼做)

例項3:
一應用在效能測試過程中,發現記憶體佔用率很高,Full GC頻繁,使用sudo -u admin -H  jmap -dump:format=b,file=檔名.hprof pid 來dump記憶體,生成dump檔案,並使用Eclipse下的mat差距進行分析,發現:


從圖中可以看出,這個執行緒存在問題,佇列LinkedBlockingQueue所引用的大量物件並未釋放,導致整個執行緒佔用記憶體高達378m,此時通知開發人員進行程式碼優化,將相關物件釋放掉即可。

說明

     本文是Java系列筆記的第4篇,這篇文章寫了近3個月,一方面是這部分對我來說也是學習階段,另一方面是這段時間一直在做專案,直到最近才比較有時間。
     本人能力有限,如果有錯漏,請留言指正。

參考資料

《深入理解Java虛擬機器:JVM高階特效與最佳實現》
JVM啟動引數大全, http://www.blogjava.net/midstr/archive/2008/09/21/230265.html
JVM系列三:JVM引數設定、分析, http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html
Java 6 JVM引數選項大全(中文版), http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm
成為JavaGC專家Part II — 如何監控Java垃圾回收機制, http://www.importnew.com/2057.html
成為Java GC專家系列(3) — 如何優化Java垃圾回收機制, http://www.importnew.com/3146.html
JDK5.0垃圾收集優化之--Don't Pause, http://calvin.iteye.com/blog/91905
Java HOTSPOT VM引數大全, http://tech.sina.com.cn/s/2009-09-23/09561077572.shtml
【原】GC的預設方式, http://iamzhongyong.iteye.com/blog/1447314
JAVA啟動引數大全之三:非Stable引數, http://blog.csdn.net/sfdev/article/details/2063928
Java虛擬機器學習 - 記憶體調優, http://blog.csdn.net/java2000_wl/article/details/8090940
記憶體溢位, http://www.open-open.com/home/space.php?uid=71669&do=blog&id=8891
如何檢視JVM的擴充套件引數:-X, http://www.blogjava.net/beansoft/archive/2012/03/01/371088.html
JVM記憶體狀況檢視方法和分析工具, http://hi.baidu.com/kingfly666666/item/e710a4371c60b0f1e7bb7a32
虛擬機器學習系列 - 附 - 虛擬機器引數, http://blog.csdn.net/su1216/article/details/7780924
 JVM系列四:生產環境引數例項及分析【生產環境例項增加中】, http://www.cnblogs.com/redcreen/archive/2011/05/05/2038331.html
垃圾收集器與記憶體分配策略, http://raging-sweet.iteye.com/blog/1170198
JVM垃圾收集器使用調查:CMS最受歡迎 , http://blog.csdn.net/wisgood/article/details/17067203
Xms Xmx PermSize MaxPermSize 區別, http://www.cnblogs.com/mingforyou/archive/2012/03/03/2378143.html
Java虛擬機器學習 - JDK視覺化監控工具, http://blog.csdn.net/java2000_wl/article/details/8049707
虛擬機器學習系列 - 6 - JDK工具, http://blog.csdn.net/su1216/article/details/7780857
JVM監控工具介紹jstack, jconsole, jinfo, jmap, jdb, jstat, http://hi.baidu.com/lotusxyhf/item/9cd8fcb8d6f8c1a5ebba935b
JVM 與 jstat, http://blog.sina.com.cn/s/blog_56fcfd620100hdcp.html


相關推薦

Java系列筆記(4)

目錄 引數設定收集器搭配啟動記憶體分配監控工具和方法調優方法調優例項     光說不練假把式,學習Java GC機制的目的是為了實用,也就是為了在JVM出現問題時分析原因並解決之。通過學習,我覺得JVM監控與調優主要的著眼點在於如何配置、如何監控、如何優化3點上。下面就將針對這3點進行學習。     (如果

馬士兵java教程筆記4

方法 isf exist sdi 馬士兵 ren 系統屬性 can dir File 類 Java.io.File 類代表系統文件名 File類的常見構造方法: Public File(String pathname)以pathname為路徑創建File對象,如果pathn

java學習筆記(4)

1:switch語句(掌握) (1)格式: switch(表示式) { case 值1: 語句體1; break; case 值2: 語句體2; break; … default: 語句體n+1; break; } 格式解釋說明: switch:說明這是swit

Thinking in Java-讀書筆記4

//第四章:控制執行流程 // 1:true和false // 2: if-else // 3: 迭代 // 3.1:do-while // 3.2:for //

Java基礎筆記4——Object、String、正則、Math、包裝類

Java基礎筆記4 八、介面API Application Programming Interfaces——應用程式介面 Object物件 是java中所有類的父類——頂級父類 沒有寫父類的都預設繼承Object類。 clone()克隆——克隆出一個新物件,新物件的屬性值和原來的一樣。 如果

Java系列筆記(5)

我想關注這個系列部落格的粉絲們都應該已經發現了,我一定是個懶蟲,在這裡向大家道歉了。這個系列的部落格是在我工作之餘寫的,經常幾天才寫一小節,不過本著寧缺毋濫的精神,所有寫的東西都是比較精煉的。這篇文章是本系列的第五篇,主要講Java執行緒相關的內容,基本上包含了執行緒要了解的比較深入的東西。技術在於積累,

Java系列筆記(6)

目錄 6,訊號量 在Java中,JVM、併發、容器、IO/NIO是我認為最重要的知識點,本章將介紹其中的併發,這也是從“會Java”到精通Java所必須經歷的一步。本章承接上一張《Java系列筆記(5) - 執行緒》,其中介紹了Java執行緒的相關知識,是本章介紹內容的基礎

Java系列筆記(3)

目錄 Java垃圾回收概況   Java GC(Garbage Collection,垃圾收集,垃圾回收)機制,是Java與C++/C的主要區別之一,作為Java開發者,一般不需要專門編寫記憶體回收和垃圾清理程式碼,對記憶體洩露和溢位的問題,也不需要像C程式設計師那樣戰戰兢兢。這是因為在Java虛擬機

Java學習筆記4. 在Linux Fedora 27中安裝Eclipse並編譯第一個源程式

1. 在Linux Fedora中安裝Eclipse比較簡單,直接在bash裡輸入:# sudo dnf install -y eclipse2. 然後等待完成,完成後輸入eclipse啟動程式,現在的版本是Oxyen23. 啟動程式後首先選擇工作目錄,這裡預設不動,單擊 L

Java 學習筆記(4)——面向對象

但是 class 本質 bool ade 編程 參數 其中 子類 現在一般的語言都支持面向對象,而java更是將其做到很過分的地步,java是強制使用面向對象的寫法,簡單的寫一個Hello Word都必須使用面向對象,這也是當初我很反感它的一點,當然現在也是很不喜歡它這一點

java基礎筆記4)----數組

adding 類型 說明 spa 初始化 pac align int nbsp 介紹: 數組是一種數據類型,是引用類型,是一塊連續的內存空間,用於存儲和管理相同類型的多個數據。 定義:-- > 數組的聲明方式 先聲明,在開辟內存空間--> int [] a;

Java 隨心筆記4

分配 數組元素 實現 內存 個數 組元 類型 new 多個 一、數組 存數(多個同種數據類型)變量只能存儲一個數據 總結:當我們遇到存儲多個數據類型的數據時,可以用數組 1)定義數組 格式:數據類型 [] 數組名 數據類型:java提供的數據類型 []:

Java並發編程實踐》筆記4——線程池高級

iss 資源 有一個 RM mic getclass term 泄露 bsp 作者:chjttony 1.估算線程池最優大小: Ncpu = CPU的數量 = Runtime.getRuntime().availableProcessors(); Ucpu = 目標C

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.2 類的繼承

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.2 類的繼承 super的使用 1.使用super訪問父類的域和方法 注意:正是由於繼承,使用this可以訪問父類的域和方法。但是有時為了明確指明父類的域和方法,就要用關鍵字super。this和super都是指當前同一個物件

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.4 訪問修飾符

修飾符(modifiers) 訪問修飾符(access modifiers) 如public/private等 其他修飾符 如abstract等 可以修飾類、也可以修飾類的成員(欄位、方法)   同一個類中 同一個包中 不同包中的子類

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.6 介面

【學習筆記】 唐大仕—Java程式設計 第4講 類、包和介面之4.6 介面 介面(interface) 介面,某種特徵的約定 定義介面interface  所有方法都自動是public abstract 實現介面implements  可以實現多繼承  與類的繼承關係無關 面向介面程式設計,而不

JAVA學習筆記系列3-JVM、JRE和JDK的區別

JVM(Java Virtual Machine)就是一個虛擬的用於執行bytecode位元組碼的“虛擬計算機”。它和os打交道 JRE(Java Runtime Environment)包含:Java虛擬機器、庫函式、執行java應用程式所必須的檔案。它包含了JVM JDK(Java Developme

《深入理解Java虛擬機器》讀書筆記4-執行時記憶體區域

Java執行時資料區域     Java執行時資料區域如下圖所示:             l  程式計數器:較小的記憶體空間,當前程式所執行的位元組碼的行號指示器。如果執行一個java方法,計數器記錄正在執行的虛擬機器位元組碼指令地址;如果

Java原始碼系列4):String,StringBuilder,StringBuffer區別

hi,國慶節後第一篇。首先,祝大家國慶節快樂,然後祝大家上班快樂。 既然上班了,那就知識學起來,今天咱說一下String,StringBuffer和StringBuilder的區別,這是面試必問,但是如果是工作了的小哥哥和小姐姐,就不會傻白甜的問這個問題,但咱還是要知道的,畢竟要

高併發程式設計系列4種常用Java執行緒鎖的特點,效能比較、使用場景

高併發程式設計系列:4種常用Java執行緒鎖的特點,效能比較、使用場景 http://youzhixueyuan.com/4-kinds-of-java-thread-locks.html   在Java併發程式設計中,經常遇到多個執行緒訪問同一個 共享資源 ,這時候作為開發者