1. 程式人生 > >自動記憶體管理機制(5)- 虛擬機器效能監控

自動記憶體管理機制(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是取樣數目

    如果省略intervalcount則代表只查詢一次。

  • 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,我這裡用第三個,輸出如下:

img

​ 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()。