前言
我們在日常的開發和維護工作中,免不了需要對JAVA程序進行監控、調優以及問題排查。
給一個系統定位問題的時候,知識、經驗是關鍵基礎,數據是依據,工具是運用知識處理數據的手段。這裏說的數據包括∶運行日誌、異常堆棧、GC日誌、線程快照(thread dump/java core文件)、堆轉儲快照(heap dump/hprof文件)等。
經常使用適當的監控和分析工具可以加快我們分析數據、定位解決問題的速度,但在學習工具前,也應當意識到工具永遠都是知識技能的一層包裝,沒有什麼工具是"祕密武器",不可能學會了就能包治百病。
進程id的獲取
許多工具或者命令需要用到java進程的進程id,有必要回顧一下。
- 查看當前運行的所有的java進程:
ps -ef|grep java
;- 準確獲取定位到tomcat下正在運行的java進程的PID命令:
ps -ef|grep java | grep catalina | awk '{print $2}'
- 準確定位到tomcat下正在運行的java進程相關信息:
ps -ef|grep java | grep catalina
.
jinfo/jmap訪問受限的解決
一般情況下,我們使用jinfo命令,可能會遇到如下的報錯:
這是因爲新版的Linux系統加入了 ptrace-scope 機制,該機制的目的是防止用戶訪問正在執行的進程的內存,但是如jinfo,jmap這些調試類工具本身就是利用ptrace來獲取執行進程的內存等信息。
解決:
- 臨時解決,該方法在下次重啓前有效:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
- 永久解決,直接修改內核參數:
sudo vi /etc/sysctl.d/10-ptrace.conf
- 編輯這行:
kernel.yama.ptrace_scope = 1
- 修改爲:
kernel.yama.ptrace_scope = 0
- 重啓系統,使修改生效。
- 編輯這行:
參數名:kernel.yama.ptrace_scope(值爲1:表示禁止用戶訪問正在執行的進程的內存;0表示可以訪問)
1 jps 顯示JVM進程信息
jps (Java Virtual Machine Process Status Tool),是java提供的一個顯示當前所有JAVA進程pid的命令,適合在linux/unix平臺上簡單察看當前java進程的一些簡單情況。
我們常常會用到unix系統裏的ps命令,這個命令主要是用來顯示當前系統的進程情況,有哪些進程以及進程id。
jps就是java程序版本的ps命令,它的作用是顯示當前系統的java進程情況及進程id。
格式:jps [-命令選項]
1.1 jps的選項
jps默認只會打印進程id和java類名,如果要更具體的信息,則要藉助更多的選項:
-
jps -q
- 只顯示pid,不顯示class名稱,jar文件名和傳遞給main方法的參數
-
jps -m
- 輸出傳遞給main方法的參數,在嵌入式jvm上可能是null
-
jps -l
- 輸出應用程序main class的完整package名或者應用程序的jar文件完整路徑名
-
jps -v
- 輸出傳遞給JVM的參數
-
jps -V
- 隱藏輸出傳遞給JVM的參數
2 jinfo 顯示JVM配置信息
jinfo 是 JDK 自帶的命令,可以用來查看正在運行的 java 應用程序的擴展參數,包括Java System屬性和JVM命令行參數;也可以動態的修改正在運行的JVM一些參數。當系統崩潰時,jinfo也可以從core文件裏面知道崩潰的Java應用程序的配置信息
Usage:
jinfo [option] <pid>
(to connect to running process)
jinfo [option] <executable <core>
(to connect to a core file)
jinfo [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both of the above
-h | -help to print this help message
格式:jinfo [-命令選項] <pid>
或 jinfo [-命令選項] <executable core>
或 jinfo [-命令選項] [server_id@] <remote ip or hostname>
pid
:對應jvm的進程idexecutable core
:產生core dump文件remote server IP or hostname
:遠程調試服務的ip或者hostnameserver-id
:唯一id,假如一臺主機上多個遠程debug服務;
Javacore,也可以稱爲“threaddump”或是“javadump”,它是 Java 提供的一種診斷特性,能夠提供一份可讀的當前運行的 JVM 中線程使用情況的快照。即在某個特定時刻,JVM 中有哪些線程在運行,每個線程執行到哪一個類,哪一個方法。 應用程序如果出現不可恢復的錯誤或是內存泄露,就會自動觸發 Javacore 的生成。
jinfo工具特別強大,有衆多的可選命令選項,比如:
2.1 輸出JVM進程的參數和屬性
jinfo <pid>
不帶任何選項的情況下,輸出當前 jvm 進程的全部參數和系統屬性
2.2 打印JVM特定參數的值
jinfo -flag <name> <pid>
用於打印虛擬機標記參數的值,name表示虛擬機標記參數的名稱。
2.3 開啓或關閉JVM特定參數
jinfo -flag [+|-]<name> <pid>
用於開啓或關閉虛擬機標記參數。+表示開啓,-表示關閉。
2.4 設置JVM特定參數的值
jinfo -flag <name>=<value> <pid>
用於設置虛擬機標記參數,但並不是每個參數都可以被動態修改的。
2.5 打印所有JVM參數
jinfo -flags <pid>
打印虛擬機參數。什麼是虛擬機參數呢?如-XX:NewSize,-XX:OldSize
等就是虛擬機參數。
2.6 打印所有系統參數
jinfo -sysprops <pid>
打印所有系統參數
3 jmap 生成內存快照文件
jmap命令是一個可以輸出所有內存中對象的工具,甚至可以將VM 中的heap,以二進制輸出成文本。打印出某個java進程(使用pid)內存內的,所有‘對象’的情況(如:產生那些對象,及其數量)。
Usage:
jmap [option] <pid>
(to connect to running process)
jmap [option] <executable <core>
(to connect to a core file)
jmap [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
64位機上使用需要使用如下方式:jmap -J-d64 -heap pid
格式:jmap [option] <pid>
或 jmap [option] <executable <core>
或 jmap [option] [server_id@]<remote server IP or hostname>
pid
:對應jvm的進程idexecutable core
:產生core dump文件remote server IP or hostname
:遠程調試服務的ip或者hostnameserver-id
:唯一id,假如一臺主機上多個遠程debug服務;
jinfo工具特別強大,有衆多的可選命令選項,比如:
3.1 輸出hprof二進制格式的heap文件
jmap -dump:live,format=b,file=myjmapfile.txt <pid>
或 jmap -dump:file=myjmapfile.hprof,format=b <pid>
使用hprof二進制形式,輸出jvm的heap內容到文件,file=可以指定文件存放的目錄。live子選項是可選的,假如指定live選項,那麼只輸出活的對象到文件。
3.2 打印正等候回收的對象的信息
jmap -finalizerinfo <pid>
打印正等候回收的對象的信息。
Number of objects pending for finalization: 0 表示等候回收的對象爲0個
3.3 打印heap的概要信息
jmap -heap <pid>
打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆內存的使用情況。
Attaching to process ID 2805, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Parallel GC with 4 thread(s) ##GC 方式
Heap Configuration: ##堆配置情況,也就是JVM參數配置的結果[平常說的tomcat配置JVM參數,就是在配置這些]
MinHeapFreeRatio = 0 ##最小堆使用比例
MaxHeapFreeRatio = 100 ##最大堆可用比例
MaxHeapSize = 734003200 (700.0MB) ##最大堆空間大小
NewSize = 21495808 (20.5MB) ##新生代分配大小
MaxNewSize = 244318208 (233.0MB) ##最大可新生代分配大小
OldSize = 43515904 (41.5MB) ##老年代大小
NewRatio = 2 ##新生代比例
SurvivorRatio = 8 ##新生代與suvivor的比例
MetaspaceSize = 21807104 (20.796875MB) ## 元數據空間大小
CompressedClassSpaceSize = 1073741824 (1024.0MB) ## 壓縮空間大小
MaxMetaspaceSize = 17592186044415 MB ## 最大元數據空間大小
G1HeapRegionSize = 0 (0.0MB) ## G1的對region空間大小
Heap Usage: ##堆使用情況【堆內存實際的使用情況】
PS Young Generation ##新生代(伊甸區Eden區 + 倖存區survior(1+2)空間)
Eden Space: ##伊甸區
capacity = 32505856 (31.0MB)
used = 0 (0.0MB)
free = 32505856 (31.0MB)
0.0% used
From Space: ##survior1區
capacity = 2621440 (2.5MB)
used = 0 (0.0MB)
free = 2621440 (2.5MB)
0.0% used
To Space: ##survior2 區
capacity = 4194304 (4.0MB)
used = 0 (0.0MB)
free = 4194304 (4.0MB)
0.0% used
PS Old Generation ##老年代使用情況
capacity = 21495808 (20.5MB)
used = 3738528 (3.565338134765625MB)
free = 17757280 (16.934661865234375MB)
17.391893340320124% used
4524 interned Strings occupying 360168 bytes.
3.4 打印每個class的實例信息
jmap -histo:live <pid>
或 jmap -histo: <pid>
打印每個class的實例數目,內存佔用,類全名信息,VM的內部類名字開頭會加上前綴”*”。如果live子參數加上後,只統計活的對象數量
採用jmap -histo pid>a.log日誌將其保存,在一段時間後,使用文本對比工具,可以對比出GC回收了哪些對象。
jmap -dump:format=b,file=outfile 3024可以將3024進程的內存heap輸出出來到outfile文件裏,再配合MAT(內存分析工具)。
3.5 打印類加載器的數據
jmap -clstats <pid>
-clstats是-permstat的替代方案,在JDK8之前,-permstat用來打印類加載器的數據。打印Java堆內存的永久保存區域的類加載器的智能統計信息。
對於每個類加載器而言,它的名稱、活躍度、地址、父類加載器、它所加載的類的數量和大小都會被打印。此外,包含的字符串數量和大小也會被打印。
3.6 指定傳遞給運行jmap的JVM的參數
jmap -J<flag> <pid>
指定傳遞給運行jmap的JVM的參數
如jmap -J-d64 -heap pid
表示在64位機上使用jmap -heap
4 jstack 輸出線程堆棧快照
jstack用於打印出給定的java進程ID或core file或遠程調試服務的Java堆棧信息(也就是線程),如果是在64位機器上,需要指定選項"-J-d64",Windows的jstack使用方式只支持以下的這種方式:jstack [-l] pid
如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕鬆地知道java程序是如何崩潰和在程序何處發生問題。
另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息,如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
(to connect to a hung process)
jstack [-m] [-l] <executable> <core>
(to connect to a core file)
jstack [-m] [-l] [server_id@]<remote server IP or hostname>
(to connect to a remote debug server)
Options:
-F to force a thread dump. Use when jstack <pid> does not respond (process is hung)
-m to print both java and native frames (mixed mode)
-l long listing. Prints additional information about locks
-h or -help to print this help message
格式:jstack [option] <pid>
或 jstack [option] <executable <core>
或 jstack [option] [server_id@]<remote server IP or hostname>
pid
:對應jvm的進程idexecutable core
:產生core dump文件remote server IP or hostname
:遠程調試服務的ip或者hostnameserver-id
:唯一id,假如一臺主機上多個遠程debug服務;
jstack工具特別強大,有衆多的可選命令選項和適用場景,比如:
4.1 程序沒有響應時強制打印線程
jstack -F <pid>
當pid對應的程序沒有響應時,強制打印線程堆棧信息。
4.2 打印完整的堆棧信息
jstack -l <pid>
長列表,打印關於鎖的附加信息,例如屬於java.util.concurrent的ownable synchronizers列表。
4.3 打印java/native框架的所有堆棧
jstack -m <pid>
打印java和native c/c++框架的所有棧信息。
4.4 jstack統計線程數
jstack -l 28367 | grep 'java.lang.Thread.State' | wc -l
4.5 jstack檢測死鎖
我們先寫個死鎖代碼
public class DeathLock {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
TimeUnit.SECONDS.sleep(1);
lock2.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
TimeUnit.SECONDS.sleep(1);
lock1.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.setName("mythread1");
t2.setName("mythread2");
t1.start();
t2.start();
}
public static void main(String[] args) {
deathLock();
}
}
這個死鎖會輸出如下日誌
"mythread2" #12 prio=5 os_prio=0 tid=0x0000000058ef7800 nid=0x1ab4 waiting on condition [0x0000000059f8f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d602d610> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at DeathLock$2.run(DeathLock.java:34)
Locked ownable synchronizers:
- <0x00000000d602d640> (a java.util.concurrent.locks.ReentrantLock$Nonfa
irSync)
"mythread1" #11 prio=5 os_prio=0 tid=0x0000000058ef7000 nid=0x3e68 waiting on condition [0x000000005947f000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d602d640> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at DeathLock$1.run(DeathLock.java:22)
Locked ownable synchronizers:
- <0x00000000d602d610> (a java.util.concurrent.locks.ReentrantLock$Nonfa
irSync)
Found one Java-level deadlock:
=============================
"mythread2":
waiting for ownable synchronizer 0x00000000d602d610, (a java.util.concurrent.l
ocks.ReentrantLock$NonfairSync),
which is held by "mythread1"
"mythread1":
waiting for ownable synchronizer 0x00000000d602d640, (a java.util.concurrent.l
ocks.ReentrantLock$NonfairSync),
which is held by "mythread2"
Java stack information for the threads listed above:
===================================================
"mythread2":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d602d610> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at DeathLock$2.run(DeathLock.java:34)
"mythread1":
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000d602d640> (a java.util.concurrent.lock
s.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInt
errupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(A
bstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(Abstrac
tQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLo
ck.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at DeathLock$1.run(DeathLock.java:22)
Found 1 deadlock.
4.6 jstack檢測cpu高
步驟一:查看cpu佔用高進程
> top
Mem: 16333644k total, 9472968k used, 6860676k free, 165616k buffers
Swap: 0k total, 0k used, 0k free, 6665292k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17850 root 20 0 7588m 112m 11m S 100.7 0.7 47:53.80 java
1552 root 20 0 121m 13m 8524 S 0.7 0.1 14:37.75 AliYunDun
3581 root 20 0 9750m 2.0g 13m S 0.7 12.9 298:30.20 java
1 root 20 0 19360 1612 1308 S 0.0 0.0 0:00.81 init
2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd
3 root RT 0 0 0 0 S 0.0 0.0 0:00.14 migration/0
步驟二:查看cpu佔用高線程
> top -H -p 17850
top - 17:43:15 up 5 days, 7:31, 1 user, load average: 0.99, 0.97, 0.91
Tasks: 32 total, 1 running, 31 sleeping, 0 stopped, 0 zombie
Cpu(s): 3.7%us, 8.9%sy, 0.0%ni, 87.4%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
Mem: 16333644k total, 9592504k used, 6741140k free, 165700k buffers
Swap: 0k total, 0k used, 0k free, 6781620k cached
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
17880 root 20 0 7588m 112m 11m R 99.9 0.7 50:47.43 java
17856 root 20 0 7588m 112m 11m S 0.3 0.7 0:02.08 java
17850 root 20 0 7588m 112m 11m S 0.0 0.7 0:00.00 java
17851 root 20 0 7588m 112m 11m S 0.0 0.7 0:00.23 java
17852 root 20 0 7588m 112m 11m S 0.0 0.7 0:02.09 java
17853 root 20 0 7588m 112m 11m S 0.0 0.7 0:02.12 java
17854 root 20 0 7588m 112m 11m S 0.0 0.7 0:02.07 java
步驟三:轉換線程ID
> printf "%x\n" 17880
45d8
步驟四:定位cpu佔用線程
jstack 17850|grep 45d8 -A 30
"pool-1-thread-11" #20 prio=5 os_prio=0 tid=0x00007fc860352800 nid=0x45d8 runnable [0x00007fc8417d2000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x00000006c6c2e708> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x00000006c6c10178> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x00000006c6c26620> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.write(PrintStream.java:527)
- eliminated <0x00000006c6c10178> (a java.io.PrintStream)
at java.io.PrintStream.print(PrintStream.java:597)
at java.io.PrintStream.println(PrintStream.java:736)
- locked <0x00000006c6c10178> (a java.io.PrintStream)
at com.demo.guava.HardTask.call(HardTask.java:18)
at com.demo.guava.HardTask.call(HardTask.java:9)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
"pool-1-thread-10" #19 prio=5 os_prio=0 tid=0x00007fc860345000 nid=0x45d7 waiting on condition [0x00007fc8418d3000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000006c6c14178> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
5 jstat 收集JVM運行數據
Jstat是JDK自帶的一個輕量級小工具。全稱“Java Virtual Machine statistics monitoring tool”,它位於java的bin目錄下,主要利用JVM內建的指令對Java應用程序的資源和性能進行實時的命令行的監控,包括了堆內存各部分的使用量,以及加載類的數量,還有垃圾回收狀況的監控。
可見,Jstat是輕量級的、專門針對JVM的工具。
格式:jstat [-命令選項] <pid>
jstat工具特別強大,有衆多的可選項,詳細查看堆內各個部分的使用量,以及加載類的數量。使用時,需加上查看進程的進程id,和所選參數。參考格式如下:
5.1 類加載統計
jstat –class <pid>
顯示加載class的數量,及所佔空間等信息。
顯示列名 | 具體描述 |
---|---|
Loaded | 裝載的類的數量 |
Bytes | 裝載類所佔用的字節數 |
Unloaded | 卸載類的數量 |
Bytes | 卸載類的字節數 |
Time | 裝載和卸載類所花費的時間 |
5.2 編譯統計
jstat -compiler <pid>
顯示VM實時編譯的數量等信息。
顯示列名 | 具體描述 |
---|---|
Compiled | 編譯任務執行數量 |
Failed | 編譯任務執行失敗數量 |
Invalid | 編譯任務執行失效數量 |
Time | 編譯任務消耗時間 |
FailedType | 最後一個編譯失敗任務的類型 |
FailedMethod | 最後一個編譯失敗任務所在的類及方法 |
5.3 垃圾回收統計
jstat -gc <pid>
顯示gc的信息,查看gc的次數,及時間。
顯示列名 | 具體描述 |
---|---|
S0C | 年輕代中第一個survivor(倖存區)的容量 (字節) |
S1C | 年輕代中第二個survivor(倖存區)的容量 (字節) |
S0U | 年輕代中第一個survivor(倖存區)目前已使用空間 (字節) |
S1U | 年輕代中第二個survivor(倖存區)目前已使用空間 (字節) |
EC | 年輕代中Eden(伊甸區)的容量 (字節) |
EU | 年輕代中Eden(伊甸區)目前已使用空間 (字節) |
OC | Old代的容量 (字節) |
OU | Old代目前已使用空間 (字節) |
PC | Perm(持久代)的容量 (字節) |
PU | Perm(持久代)目前已使用空間 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
YGCT | 從應用程序啓動到採樣時年輕代中gc所用時間(s) |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
FGCT | 從應用程序啓動到採樣時old代(full gc)gc所用時間(s) |
GCT | 從應用程序啓動到採樣時gc用的總時間(s) |
5.4 堆內存統計
jstat -gccapacity <pid>
顯示VM內存中三代(young,old,perm)對象的使用和佔用大小
顯示列名 | 具體描述 |
---|---|
NGCMN | 年輕代(young)中初始化(最小)的大小(字節) |
NGCMX | 年輕代(young)的最大容量 (字節) |
NGC | 年輕代(young)中當前的容量 (字節) |
S0C | 年輕代中第一個survivor(倖存區)的容量 (字節) |
S1C | 年輕代中第二個survivor(倖存區)的容量 (字節) |
EC | 年輕代中Eden(伊甸區)的容量 (字節) |
OGCMN | old代中初始化(最小)的大小 (字節) |
OGCMX | old代的最大容量(字節) |
OGC | old代當前新生成的容量 (字節) |
OC | old代的容量 (字節) |
PGCMN | perm代中初始化(最小)的大小 (字節) |
PGCMX | perm代的最大容量 (字節) |
PGC | perm代當前新生成的容量 (字節) |
PC | Perm(持久代)的容量 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
5.5 新生代垃圾回收統計
jstat -gcnew <pid>
統計年輕代對象的信息
顯示列名 | 具體描述 |
---|---|
S0C | 年輕代中第一個survivor(倖存區)的容量 (字節) |
S1C | 年輕代中第二個survivor(倖存區)的容量 (字節) |
S0U | 年輕代中第一個survivor(倖存區)目前已使用空間 (字節) |
S1U | 年輕代中第二個survivor(倖存區)目前已使用空間 (字節) |
TT | 持有次數限制 |
MTT | 最大持有次數限制 |
DSS | 期望的倖存區大小 |
EC | 年輕代中Eden(伊甸區)的容量 (字節) |
EU | 年輕代中Eden(伊甸區)目前已使用空間 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
YGCT | 從應用程序啓動到採樣時年輕代中gc所用時間(s) |
5.6 新生代內存統計
jstat -gcnewcapacity <pid>
統計年輕代對象的信息及其佔用量。
顯示列名 | 具體描述 |
---|---|
NGCMN | 年輕代(young)中初始化(最小)的大小(字節) |
NGCMX | 年輕代(young)的最大容量 (字節) |
NGC | 年輕代(young)中當前的容量 (字節) |
S0CMX | 年輕代中第一個survivor(倖存區)的最大容量 (字節) |
S0C | 年輕代中第一個survivor(倖存區)的容量 (字節) |
S1CMX | 年輕代中第二個survivor(倖存區)的最大容量 (字節) |
S1C | 年輕代中第二個survivor(倖存區)的容量 (字節) |
ECMX | 年輕代中Eden(伊甸區)的最大容量 (字節) |
EC | 年輕代中Eden(伊甸區)的容量 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
5.7 老年代垃圾回收統計
jstat -gcold <pid>
統計老年代對象的信息
顯示列名 | 具體描述 |
---|---|
MC | 方法區大小 |
MU | 方法區使用大小 |
CCSC | 壓縮類空間大小 |
CCSU | 壓縮類空間使用大小 |
OC | Old代的容量 (字節) |
OU | Old代目前已使用空間 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
YGCT | 從應用程序啓動到採樣時年輕代中gc所用時間(s) |
GCT | 從應用程序啓動到採樣時gc用的總時間(s) |
5.8 老年代內存統計
jstat -gcoldcapacity <pid>
統計老年代對象的信息及其佔用量
顯示列名 | 具體描述 |
---|---|
OGCMN | old代中初始化(最小)的大小 (字節) |
OGCMX | old代的最大容量(字節) |
OGC | old代當前新生成的容量 (字節) |
OC | Old代的容量 (字節) |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
YGCT | 從應用程序啓動到採樣時年輕代中gc所用時間(s) |
GCT | 從應用程序啓動到採樣時gc用的總時間(s) |
5.9 元數據空間統計
jstat -gcmetacapacity <pid>
統計元數據空間容量
顯示列名 | 具體描述 |
---|---|
MCMN | 最小元數據容量 |
MCMX | 最大元數據容量 |
MC | 方法區大小 |
CCSMN | 最小壓縮類空間大小 |
CCSMX | 最大壓縮類空間大小 |
CCSC | 壓縮類空間大小 |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
FGCT | 從應用程序啓動到採樣時old代(full gc)gc所用時間(s) |
GCT | 從應用程序啓動到採樣時gc用的總時間(s) |
5.10 總結垃圾回收統計
jstat -gcutil <pid>
統計gc容量佔比信息
顯示列名 | 具體描述 |
---|---|
S0 | 年輕代中第一個survivor(倖存區)已使用的佔當前容量百分比 |
S1 | 年輕代中第二個survivor(倖存區)已使用的佔當前容量百分比 |
E | 年輕代中Eden(伊甸區)已使用的佔當前容量百分比 |
O | old代已使用的佔當前容量百分比 |
P | perm代已使用的佔當前容量百分比 |
YGC | 從應用程序啓動到採樣時年輕代中gc次數 |
YGCT | 從應用程序啓動到採樣時年輕代中gc所用時間(s) |
FGC | 從應用程序啓動到採樣時old代(full gc)gc次數 |
FGCT | 從應用程序啓動到採樣時old代(full gc)gc所用時間(s) |
GCT | 從應用程序啓動到採樣時gc用的總時間(s) |
5.11 JVM編譯方法統計
jstat -printcompilation <pid>
統計 JVM編譯方法的信息
顯示列名 | 具體描述 |
---|---|
Compiled | 最近編譯方法的數量 |
Size | 最近編譯方法的字節碼數量 |
Type | 最近編譯方法的編譯類型。 |
Method | 方法名標識 |
6 jhat 堆快照文件可視化工具
jhat(Java Virtual Machine Heap Analysis Tool)虛擬機堆轉儲快照分析工具,也是jdk內置的工具之一,是個用來分析java堆內存的命令,它會建立一個HTTP/HTML服務器,讓用戶可以在瀏覽器上查看分析結果,包括對象的數量,大小等等,並支持對象查詢語言(OQL)。
jhat的作用對象是堆快照文件,也就是dump文件或者hprof文件,文件生成後,我們再使用jaht進行分析。
-
使用jmap命令獲取java程序堆快照(生成dump文件)
-
使用jconsole選項通過
HotSpotDiagnosticMXBean
從運行時獲得堆快照(生成dump文件) -
虛擬機啓動時如果指定了
-XX:+HeapDumpOnOutOfMemoryError
選項, 則在拋出OutOfMemoryError時, 會自動執行堆快照(生成dump文件) -
使用 hprof 命令獲得hprof文件(生成hprof文件)
用法:jhat [ options ] heap-dump-file
,如jhat -J-Xmx512M app.dump
option具體選項及作用如下:
- -J< flag > 因爲 jhat 命令實際上會啓動一個JVM來執行, 通過 -J 可以在啓動JVM時傳入一些啓動參數. 例如, -J-Xmx512m 則指定運行 jhat 的Java虛擬機使用的最大堆內存爲 512 MB. 如果需要使用多個JVM啓動參數,則傳入多個 -Jxxxxxx
- -stack false|true 關閉跟蹤對象分配調用堆棧。如果分配位置信息在堆轉儲中不可用. 則必須將此標誌設置爲 false. 默認值爲 true.
- -refs false|true 關閉對象引用跟蹤。默認情況下, 返回的指針是指向其他特定對象的對象,如反向鏈接或輸入引用(referrers or incoming references), 會統計/計算堆中的所有對象。
- -port port-number 設置 jhat HTTP server 的端口號. 默認值 7000。
- -exclude exclude-file 指定對象查詢時需要排除的數據成員列表文件。 例如, 如果文件列出了 java.lang.String.value , 那麼當從某個特定對象 Object o 計算可達的對象列表時, 引用路徑涉及 java.lang.String.value 的都會被排除。
- -baseline exclude-file 指定一個基準堆轉儲(baseline heap dump)。 在兩個 heap dumps 中有相同 object ID 的對象會被標記爲不是新的(marked as not being new). 其他對象被標記爲新的(new). 在比較兩個不同的堆轉儲時很有用。
- -debug int 設置 debug 級別. 0 表示不輸出調試信息。 值越大則表示輸出更詳細的 debug 信息。
- -version 啓動後只顯示版本信息就退出。
有時dump出來的堆很大,在啓動時會報堆空間不足的錯誤,可加參數:
jhat -J-Xmx512m <heap dump file>
。這個內存大小可根據自己電腦進行設置。
不過實事求是地說,在實際工作中,除非真的沒有別的工具可用,否則一般不會去直接使用jhat命令來分析demp文件,主要原因有二:
- 一是一般不會在部署應用程序的服務器上直接分析dump文件,即使可以這樣做,也會盡量將dump文件拷貝到其他機器上進行分析,因爲分析工作是一個耗時且消耗硬件資源的過程,既然都要在其他機器上進行,就沒必要受到命令行工具的限制了;
- 另外一個原因是jhat的分析功能相對來說很簡陋,VisualVM以及專門分析dump文件的Eclipse Memory Analyzer、IBM HeapAnalyzer等工具,都能實現比jhat更強大更專業的分析功能。
6.1 jhat工具的開啓
- 使用jps獲取java應用的pid
$ jps
> 17904 -- process information unavailable
> 40836 Jps
> 43228 -- process information unavailable
- 使用jmap獲取dump文件
$ jmap -dump:file=test.dump,format=b 43228
> Dumping heap to D:\projects\i-lupro-app\test.dump ...
> Heap dump file created
- 使用jhat分析dump文件
$ jhat -J-Xmx512M test.dump
> Reading from test.dump...
> Dump file created Wed Nov 25 18:48:51 CST 2020
> Snapshot read, resolving...
> Resolving 197329 objects...
> Chasing references, expect 39 dots.......................................
> Eliminating duplicate references.......................................
> Snapshot resolved.
> Started HTTP server on port 7000
> Server is ready.
- 在瀏覽器打開
http://localhost:7000/
開啓可視化工具
6.2 jhat工具的功能
6.2.1 顯示出堆中所包含的所有的類
6.2.2 從根集能引用到的對象
6.2.3 顯示平臺包括的所有類的實例數量
6.2.4 堆實例的分佈表
6.2.5 執行對象查詢語句(OQL)
其輸入內容如:
# 查詢長度大於100的字符串
select s from java.lang.String s where s.count > 100
詳細的OQL可點擊上圖的“OQL help”
7 jconsole 可視化監控控制檯
Jconsole(Java Monitoring and Management Console),一種基於JMX的可視化監視、管理工具。
7.1 啓動JConsole
- 點擊JDK/bin 目錄下面的jconsole.exe 即可啓動
- 然後會自動自動搜索本機運行的所有虛擬機進程。
- 選擇其中一個進程可開始進行監控
7.2 JConsole介紹
JConsole 基本包括以下基本功能:概述、內存、線程、類、VM概要、MBean
7.2.1 概覽
7.2.2 內存監控
內存頁籤相對於可視化的jstat 命令,用於監視受收集器管理的虛擬機內存。
jconsole可監控的內存有許多,如下圖
我們以堆內存爲例:
選項 | 值 | 描述 |
---|---|---|
堆內存的大小 | 442032KB | |
已使用 | 249362KB | 目前使用的內存量,包括所有對象,可達和不可達佔用的內存。 |
已提交 | 442032KB | 保證由Java虛擬機使用的內存量。 提交的內存量可能會隨時間而改變。 Java虛擬機可能會釋放系統內存,並已提交的內存量可能會少於最初啓動時分配的內存量。 提交的內存量將始終大於或等於使用的內存量。 |
最大值 | 742400KB | 可用於內存管理的最大內存量。 它的價值可能會發生變化,或者是不確定的。 如果Java虛擬機試圖增加使用的內存要大於提交的內存,內存分配可能失敗,即使使用量小於或等於最大值(例如,當系統上的虛擬內存不足)。 |
GC時間 | parnew上的 3.487s(73收集) | 累計時間花在垃圾收集和調用的總數。 它可能有多個行,其中每一個代表一個垃圾收集器算法在Java虛擬機的總耗時和執行次數 |
堆 | -- | 堆內存是運行時數據區域,Java VM的所有類實例和數組分配內存。 可能是固定或可變大小的堆。 |
非堆內存 | -- | 非堆內存包括在所有線程和Java虛擬機內部處理或優化所需的共享的方法。 它存儲了類的結構,運行常量池,字段和方法數據,以及方法和構造函數的代碼,方法區在邏輯上是堆的一部分,看具體實現的方式。根據實現方式的不同,Java虛擬機可能不進行垃圾收集或壓縮。 堆內存一樣,方法區域可能是一個固定或可變大小。 方法區的內存不需要是連續的。 |
除了方法區,Java虛擬機可能需要進行內部處理或優化,這也屬於非堆內存的內存。 例如,實時(JIT)編譯器需要內存用於存儲從Java虛擬機的高性能的代碼翻譯的機器碼。
7.2.3 線程監控
如果上面的“內存”頁籤相當於可視化的jstat命令的話,“線程”頁籤的功能相當於可視化的jstack命令,遇到線程停頓時可以使用這個頁籤進行監控分析。
在左下角的“線程”列表列出了所有的活動線程。 如果你輸入一個“filter”字段中的字符串,線程列表將只顯示其名稱中包含你輸入字符串線程。 點擊一個線程在線程列表的名稱,顯示該線程的信息的權利,包括線程的名稱,狀態、阻塞和等待的次數、堆棧跟蹤。
如果要檢查您的應用程序已經陷入死鎖的線程,可以通過點擊“檢測死鎖”按鈕檢測。線程長時間停頓的主要原因主要有:等待外部資源(數據庫連接、網絡資源、設備資源等)、死循環、鎖等待(活鎖和死鎖)
我們寫個死鎖代碼
package com.jvm;
/**
* 線程死鎖驗證
*/
public class JConsoleThreadLock {
/**
* 線程死鎖等待演示
*/
static class SynAddRunalbe implements Runnable {
int a, b;
public SynAddRunalbe(int a, int b) {
this.a = a;
this.b = b;
}
@Override
public void run() {
synchronized (Integer.valueOf(a)) {
synchronized (Integer.valueOf(b)) {
System.out.println(a + b);
}
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new SynAddRunalbe(1, 2)).start();
new Thread(new SynAddRunalbe(2, 1)).start();
}
}
}
這段代碼開了200個線程去分別計算1+2以及2+1的值,其實for循環是可省略的,兩個線程也可能會導致死鎖,不過那樣概率太小,需要嘗試運行很多次才能看到效果。一般的話,帶for循環的版本最多運行2~3次就會遇到線程死鎖,程序無法結束。
造成死鎖的原因是Integer.valueOf()方法基於減少對象創建次數和節省內存的考慮,
[-128,127]
之間的數字會被緩存,當valueOf()方法傳入參數在這個範圍之內,將直接返回緩存中的對象。也就是說,代碼中調用了200次Integer.valueOf()方法一共就只返回了兩個不同的對象。假如在某個線程的兩個synchronized塊之間發生了一次線程切換,那就會出現線程A等着被線程B持有的Integer.valueOf(1),線程B又等着被線程A持有的Integer.valueOf(2),結果出現大家都 跑不下去的情景。
如果檢測到任何死鎖的線程,這些都顯示在一個新的標籤,旁邊出現的“死鎖”標籤, 在圖所示。
結果描述:顯示了線程Thread-53在等待一個被線程Thread-66持有Integer對象,而點擊線程Thread-66則顯示它也在等待一個Integer對象,被線程Thread-53持有,這樣兩個線程就互相卡住,都不存在等到鎖釋放的希望了
7.2.4 類加載信息監控
類”標籤顯示關於類加載的信息。
- 紅線表示加載的類的總數(包括後來卸載的)
- 藍線是當前的類加載數
在選項卡底部的詳細信息部分顯示類的加載,因爲Java虛擬機開始的總數,當前加載和卸載的數量。** 跟蹤**類加載詳細的輸出,您可以勾選在頂部的右上角複選框。
7.2.5 VM概要監控
在此選項卡中提供的信息包括以下內容。
- 摘要
- 運行時間 :開始以來,Java虛擬機的時間總額。
- 進程的CPU時間 :Java VM的開始,因爲它消耗的CPU時間總量。
- 編譯總時間 :累計時間花費在JIT編譯。
- 主題
- 活動線程 :目前現場守護線程,加上非守護線程數量。
- 峯值 :活動線程的最高數目,因爲Java虛擬機開始。
- 守護線程 :當前的活動守護線程數量。
- 總線程 :開始自Java虛擬機啓動的線程總數,包括非守護進程,守護進程和終止的線程。
- 類
- 當前類裝載 :目前加載到內存中的類數目。
- 總類加載 :從Java VM開始加載到內存中的類總和,包括那些後來被卸載的類。
- 已卸載類總數 :從Java虛擬機開始從內存中卸載的類的數目。
- 內存
- 當前的堆大小 :目前所佔用的堆的千字節數。
- 分配的內存 :堆分配的內存總量。
- 最大堆最大值 :堆所佔用的千字節的最大數目。
- 待最後確定的對象:待最後確定的對象的數量。
- 花在執行GC的垃圾收集器 :包括垃圾收集,垃圾收集器的名稱,進行藏品的數量和總時間的信息。
- 操作系統
- 總物理內存
- 空閒物理內存
- 分配的虛擬內存
- 其他信息
- VM參數 :輸入參數的應用程序通過Java虛擬機,不包括的主要方法的參數。
- 類路徑是由系統類加載器用於搜索類文件的類路徑。
- 庫路徑 :加載庫時要搜索的路徑列表。
- 引導類路徑 :引導類路徑是由引導類加載器用於搜索類文件。
8 jvisualvm
jvisualvm是Netbeans的profile子項目,從JDK6.0 update 7 版本開始自帶。jvisualvm同jconsole一樣,都是一個基於圖形化界面的、可以查看本地及遠程的JAVA GUI監控工具,jvisualvm是一個綜合性的分析工具,其整合了jstack、jmap、jinfo等衆多調試工具的功能,可以認爲jvisualvm是jconsole的升級版。
8.1 啓動jvisualvm
在JDK_HOME/bin下雙擊jvisualvm.exe,或者直接在命令行中輸入jvisualvm
都可
我們可以看到側邊框:
- 本地:如果你本地有java進程啓動了,那麼在本地這個欄目就會顯示。
- 遠程:監控的遠程主機
- 快照:裝載dump文件或者hprof文件,進行分析
由於本地和遠程展示的監控界面都是相同的,接下來我們直接介紹遠程。
8.2 添加遠程監控
注意,一個主機如果希望支持遠程監控,需要在啓動時添加以下參數:
-Dcom.sun.management.jmxremote.port=1099
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
右擊"遠程"-->"添加遠程主機",出現界面:
在連接後面添加一個1099,這是遠程主機jmx監聽的端口號,點擊確定,側邊欄變爲:
點擊紅色框中的jmx連接,出現以下界面:
8.3 jvisualvm介紹
jvisualvm分爲四個選項卡:概述、監視、線程、抽樣器,下面我們一一介紹:
8.3.1 概述頁
默認顯示的就是概述選項卡,其中的信息相當於我們調用了jinfo命令獲得,其還包含了兩個子選項卡:
- jvm參數欄:相當於我們調用
jinfo -flags <pid>
獲得 - 系統屬性欄:相當於我們調用
jinfo -sysprops <pid>
獲得
8.3.2 監視頁
主要顯示了cpu、內存使用、類加載信息、線程信息等,這只是一個概要性的介紹,如下圖:
點擊右上角的"堆dump"會在遠程主機上,dump一個內存映射文件,之所以不直接dump到本地,主要是因爲這個文件通常比較大,直接dump到本地會很慢。
dump完成之後,可以手工下載這個文件,通過"文件"->"裝入"來進行分析。
8.3.3 線程頁
線程選項卡列出了所有線程的信息,並使用了不同的顏色標記,右下角的顏色表示了不同的狀態。
右上角的線程dump會直接把線程信息dump到本地,相當於調用了jstack命令,如:
8.3.4 抽樣器頁
主要有"cpu"和"內存"兩個按鈕,功能類似,只不過一個是抽樣線程佔用cpu的情況,一個是抽樣jvm對象內存的情況。
- 通過設置可以對CPU的採樣來源以及內存的刷新時間進行設置;
- 點擊CPU或者Memory即可開始監控,點擊Stop則停止採樣;
我們以分析cpu波動爲例,看下如何使用cpu採樣器:
8.3.4.1 分析CPU波動問題
進入抽樣器頁(Sampler),在CPU波動的時候點擊CPU對CPU進行抽樣。
注意線上環境千萬不要使用Sampler右邊的Profiler
抽樣進行一段時間後(建議3分鐘左右就行了,時間越長生成的snapshot越大),點擊”stop”,然後點擊”snapshot”生成快照
生成快照後按照”Total Time(CPU)”排序,找到那些線程最耗費CPU,從下圖中我們看到基本上都是DubboServerHandler,熟悉Dubbo框架的知道這都是我們的業務線程。
那麼我們對這些線程進行分析(多分析幾個線程,雙擊指定線程就可以看這個線程的調用棧以及耗時情況),看看這些線程在哪裏比較耗費CPU。
通過分析發現,在Dubbo遠程調用的時候驗證參數的時間比我們處理業務的時間都長(見下圖紅色方框框起來的方法)。結合Dubbo官方文檔得知,Dubbo的參數驗證這個特性是比較耗費性能的,而我們的接口參數使用了javax.validation註解來驗證參數。所以我們在調用的時候使用validation=”false”禁止使用參數驗證這個特性就好了CPU就回歸正常了。
除此之外,我們也可以動態的觀察線程的變化,功能有點類似JProfiler的“Mark Current Values”。我們點擊線程CPU時間
這個tab。查看每個線程佔用cpu時間的增量數據。
8.3.4.2 內存採樣
和cpu採樣一樣的,樣進行一段時間後(建議3分鐘左右就行了,時間越長生成的snapshot越大),點擊”stop”,然後點擊”snapshot”生成快照
點擊增量同樣可以監控內存的變動情況:
點擊“執行GC”,則可以手動觸發GC; 點擊“堆Dump”,則可以手動觸發dump文件生成;