自動記憶體管理機制(5)- 虛擬機器效能監控
自動記憶體管理機制(5)- 虛擬機器效能監控
0. 概述
在我們日常開發的專案中,有時經常會碰到以下問題:
- OOM(OutOfMemoryError),記憶體不足
- 記憶體洩漏
- 執行緒死鎖
- Lock Contention,鎖爭用
- Java程序消耗CPU過高…
通常我們使用的最簡單的解決方法就是調大記憶體,但這樣的話只是解決了這一次的問題,對於它為什麼會出現的原因就置之不理了(或者說不會去深究問題根源)。本文將對一些常用的JVM效能調優、監控工具進行介紹,經常使用適當的虛擬機器監控和分析工具可以加快我們分析資料、定位解決問題的速度。
一共有以下幾種常用的JDK監控和故障處理工具:
名稱 | 主要作用 |
---|---|
jps | JVM Process Status Tool,顯示制定系統內所有的HotSpot虛擬機器程序 |
jstat | JVM Statistics Monitoring Tool,用於收集HotSpot虛擬機器各方面的執行資料 |
jinfo | Configuration Info for Java,顯示虛擬機器配置資訊 |
jmap | Memory Map for Java,生成虛擬機器的記憶體轉儲快照(heapdump檔案) |
jhat | JVM Heap Dump Browser,用於分析heapdump檔案,它會建立一個HTTP/HTML伺服器,讓使用者可以在瀏覽器上檢視分析結果 |
jstack | Stack Trace for Java,顯示虛擬機器的執行緒快照 |
注意:以下所有內容均基於JDK1.8進行測試
1. jps:虛擬機器程序狀況工具
jps命令和UNIX的ps命令非常像:可以列出正在執行的虛擬機器程序,並顯示虛擬機器執行主類(Main Class,main()函式所在的類)名稱以及這些程序的本地虛擬機器的唯一ID。
語法如下:
jps [options] [hostid]
如果不指定hostid
就預設為當前主機或者伺服器。
命令列引數選項options
說明如下:
選項 | 作用 |
---|---|
-q | 只輸出LVMID,省略主類的名稱 |
-m | 輸出虛擬機器程序啟動時傳遞給主類main()函式的引數 |
-l | 輸出主類的全名,如果程序執行的時Jar包,輸出Jar路徑 |
-v | 輸出虛擬機器程序啟動時JVM引數 |
執行結果:
[C:\~]$ jps -l
9328 org.jetbrains.jps.cmdline.Launcher
10520 org.jetbrains.idea.maven.server.RemoteMavenServer
7912 sun.tools.jps.Jps
1580 com.yis.catcher.demo.Demo1
7308
2. jstat:虛擬機器統計資訊監視工具
jstat(JVM Statistics Monitoring Tool)是用來監視虛擬機器各種執行狀態資訊的命令列工具。它可以顯示虛擬機器程序中的類載入、記憶體、垃圾收集、JIT編譯等執行資料。
語法如下:
jstat [option vmid [interval[s|ms] [count]] ]
-
vimd
是Java虛擬機器ID,在Linux/Unix系統上一般就是程序ID。 -
interval
是取樣時間間隔。 -
count
是取樣數目如果省略
interval
和count
則代表只查詢一次。 -
option
代表型別,主要分為三類:類載入、垃圾收集、執行期編譯情況,有以下的具體選項:
比如下面輸出的是GC資訊,每隔250毫秒查詢一次,一共查詢5次:
[C:\~]$ jstat -gc 1580 250 5
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
5120.0 5120.0 0.0 0.0 33280.0 30803.8 87552.0 0.0 4480.0 775.8 384.0 76.4 0 0.000 0 0.000 0.000
先來看JVM堆記憶體的分佈:
堆記憶體 = 新生代 + 老年代 + 永久代(方法區)
新生代 = Eden區 + 兩個Suivivor區(From 和 To)
現在再來看上面具體的各項含義:
S0C、S1C、S0U、S1U:Survivor 0/1區容量(Capacity)和使用量(Used)
EC、EU:Eden區容量和使用量
OC、OU:年老代容量和使用量
MC、MU:永久代容量和使用量
CCSC、CCSU:壓縮類容量和使用量
YGC、YGT:年輕代GC次數和GC耗時
FGC、FGCT:Full GC次數和Full GC耗時
GCT:GC總耗時
3. jinfo:Java配置資訊工具
jinfo是用來實時檢視和調整虛擬機器各項引數。
語法如下:
jinfo [option] pid
執行樣例:查詢CMSinitiatingOccupancyFraction引數值
[C:\~]$ jinfo -flag CMSInitiatingOccupancyFraction 1580
-XX:CMSInitiatingOccupancyFraction=-1
對於Windows平臺,jinfo的的功能仍有較大限制,只提供了最基本的-flag
選項
4. jmap:Java記憶體映像工具
jmap命令用於生成堆轉儲快照,檢視堆記憶體使用狀況,一般配合jhat使用。
語法如下:
jmap [option] vmid
option
可用的選項如下:
jmap的功能在windos平臺下是受限的,除了-dump
和-histo
可用,其他都不可用。
jmap進行dump命令格式如下:
jmap -dump:format=b,file=dumpFileName pid
對10424程序進行Dump:
[C:\~]$ jmap -dump:format=b,file=E:\dumpDemo.dat 10424
Dumping heap to E:\dumpDemo.dat ...
Heap dump file created
5. jhat:虛擬機器堆轉儲快照分析工具
jhat命令與jmap搭配使用,來分析jmap生成的堆轉儲快照。
示例:
[C:\Users\Yisany\Desktop]$ jhat -port 9998 dumpDemo.dat
Reading from dumpDemo.dat...
Dump file created Wed Nov 14 10:36:30 CST 2018
Snapshot read, resolving...
Resolving 19457 objects...
Chasing references, expect 3 dots...
Eliminating duplicate references...
Snapshot resolved.
Started HTTP server on port 9998
Server is ready.
此時在瀏覽器中輸入 http://localhost:9998/ 就可以看到結果。
6. jstack:Java堆疊跟蹤工具
jstack用於生成虛擬機器當前時刻的執行緒快照。生成執行緒快照的主要目的是定位執行緒出現長時間停頓的原因。
執行緒快照:當前虛擬機器每一條執行緒正在執行的方法堆疊的集合。
執行緒出現停頓的時候通過jstack來檢視各個執行緒的呼叫堆疊,就可以知道沒有響應的執行緒到底在做什麼。
語法如下:
jstack [option] pid
option
的可選項如下:
以下摘自JVM效能調優監控工具jps、jstack、jmap、jhat、jstat、hprof使用詳解
jstack可以定位到執行緒堆疊,根據堆疊資訊我們可以定位到具體程式碼,所以它在JVM效能調優中使用得非常多。下面我們來一個例項找出某個Java程序中最耗費CPU的Java執行緒並定位堆疊資訊,用到的命令有ps、top、printf、jstack、grep。
第一步先找出Java程序ID,我部署在伺服器上的Java應用名稱為mrf-center:
[email protected]:/# ps -ef | grep mrf-center | grep -v grep
root 21711 1 1 14:47 pts/3 00:02:10 java -jar mrf-center.jar
得到程序ID為21711,第二步找出該程序內最耗費CPU的執行緒,可以使用ps -Lfp pid或者ps -mp pid -o THREAD, tid, time或者top -Hp pid,我這裡用第三個,輸出如下:
TIME列就是各個Java執行緒耗費的CPU時間,CPU時間最長的是執行緒ID為21742的執行緒,用
printf "%x\n" 21742
得到21742的十六進位制值為54ee,下面會用到。
OK,下一步終於輪到jstack上場了,它用來輸出程序21711的堆疊資訊,然後根據執行緒ID的十六進位制值grep,如下:
[email protected]:/# jstack 21711 | grep 54ee
"PollIntervalRetrySchedulerThread" prio=10 tid=0x00007f950043e000 nid=0x54ee in Object.wait() [0x00007f94c6eda000]
可以看到CPU消耗在PollIntervalRetrySchedulerThread這個類的Object.wait(),我找了下我的程式碼,定位到下面的程式碼:
// Idle wait
getLog().info("Thread [" + getName() + "] is idle waiting...");
schedulerThreadState = PollTaskSchedulerThreadState.IdleWaiting;
long now = System.currentTimeMillis();
long waitTime = now + getIdleWaitTime();
long timeUntilContinue = waitTime - now;
synchronized(sigLock) {
try {
if(!halted.get()) {
sigLock.wait(timeUntilContinue);
}
}
catch (InterruptedException ignore) {
}
}
它是輪詢任務的空閒等待程式碼,上面的sigLock.wait(timeUntilContinue)就對應了前面的Object.wait()。