1.通過jstat命令進行查看堆內存使用情況
先隨便啟動一個(java的應用程序就行)Tomcat服務,在命令行里輸入jps -l命令查看進程號
1.1 查看class加載統計
jstat -class 進程號
說明:
- Loaded:加載class的數量
- Bytes:所占用空間大小
- Unloaded:未加載數量
- Bytes:未加載占用空間
- Time:時間
1.2查看編譯統計
jstat -compiler 進程號
- Compiled:編譯數量。
- Failed:失敗數量
- Invalid:不可用數量
- Time:時間
- FailedType:失敗類型
- FailedMethod:失敗的方法
1.3垃圾回收統計
jstat -gc 進程號 間隔時間(毫秒) 打印次數
統計一次
每過1000毫秒統計一次,一共統計三次
說明:
- S0C:第一個Survivor區的大小(KB)
- S1C:第二個Survivor區的大小(KB)
- S0U:第一個Survivor區的使用大小(KB)
- S1U:第二個Survivor區的使用大小(KB)
- EC:Eden區的大小(KB)
- EU:Eden區的使用大小(KB)
- OC:Old區大小(KB)
- OU:Old使用大小(KB)
- MC:方法區大小(KB)
- MU:方法區使用大小(KB)
- CCSC:壓縮類空間大小(KB)
- CCSU:壓縮類空間使用大小(KB)
- YGC:年輕代垃圾回收次數
- YGCT:年輕代垃圾回收消耗時間
- FGC:老年代垃圾回收次數
- FGCT:老年代垃圾回收消耗時間
- GCT:垃圾回收消耗總時間
2.使用jmap對內存進行分析
2.1查看堆內存情況
2.2查看內存中對象數量及大小
查看所有對象,包括活躍以及非活躍的
jmap ‐histo <進程號> | more
只查看活躍的對象
jmap ‐histo:live <進程號> | more
對象說明
- B byte
- C char
- D double
- F float
- I int
- J long
- Z boolean
- [ 數組,如[I表示int[]
- [L+類名 其他對象
2.3將內存使用情況dump到文件中
jmap ‐dump:format=b,file=dumpFileName <pid>
2.4通過jhat對dump文件進行分析
一個未被占用的端口就行了
jhat ‐port <port> <file>
啟動成功如下圖
使用瀏覽器訪問本機的9999(自己設置的)端口
網頁中顯示的就是所有的對象,直接拉到最下面可以使用OQL進行查詢
OQL語法類似於SQL語句,如果不會使用可以查看幫助
查詢字符串長度大於等於1000的對象
select s from java.lang.String s where s.value.length>=1000
這樣對dump文件進行分析的結果還是有些不太清晰,接下來
3.使用MAT工具對dump文件進行分析
工具下載地址 下載和自己jdk對應位數的
下載完成,解壓並打開
4.內存泄漏定位與分析
編寫導致內存泄漏的代碼,啟動時修改內存大小
import java.util.ArrayList; import java.util.List; import java.util.UUID; public class TestJvmOutOfMemory { public static void main(String[] args) { List<Object> list = new ArrayList<>(); for (int i = 0; i < 10000000; i++) { String str = ""; for (int j = 0; j < 1000; j++) { str += UUID.randomUUID().toString(); } list.add(str); } System.out.println("ok"); } }
JVM參數如下
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
執行一段時間程序停止運行,拋出異常,並在項目的根目錄下生成一個文件
接下來使用MAT工具分析上面的文件
由上圖可見,占用了總內存的90%導致了內存不足程序的退出,這種情況下不適合修改JVM參數,應當找到導致問題所在的代碼,進行調整
點擊上圖中的Details可以看見每個對象占用的多大內存
5.使用jstack監控死鎖問題
jstack <pid>
先來了解一下線程的狀態
初始態(NEW)
創建一個Thread對象,但還未調用start()啟動線程時,線程處於初始態。
運行態(RUNNABLE),在Java中,運行態包括 就緒態 和 運行態。
就緒態
該狀態下的線程已經獲得執行所需的所有資源,只要CPU分配執行權就能運 行。
所有就緒態的線程存放在就緒隊列中。
運行態
獲得CPU執行權,正在執行的線程。
由於一個CPU同一時刻只能執行一條線程,因此每個CPU每個時刻只有一條 運行態的線程。
阻塞態(BLOCKED)
當一條正在執行的線程請求某一資源失敗時,就會進入阻塞態。
而在Java中,阻塞態專指請求鎖失敗時進入的狀態。
由一個阻塞隊列存放所有阻塞態的線程。
處於阻塞態的線程會不斷請求資源,一旦請求成功,就會進入就緒隊列,等待執 行。
等待態(WAITING)
當前線程中調用wait、join、park函數時,當前線程就會進入等待態。
也有一個等待隊列存放所有等待態的線程。
線程處於等待態表示它需要等待其他線程的指示才能繼續運行。
進入等待態的線程會釋放CPU執行權,並釋放資源(如:鎖)
超時等待態(TIMED_WAITING)
當運行中的線程調用sleep(time)、wait、join、parkNanos、parkUntil時,就 會進入該狀態;
它和等待態一樣,並不是因為請求不到資源,而是主動進入,並且進入后需要其 他線程喚醒;
進入該狀態后釋放CPU執行權 和 占有的資源。
與等待態的區別:到了超時時間后自動進入阻塞隊列,開始競爭鎖。
終止態(TERMINATED)
線程執行結束后的狀態。
模擬死鎖的代碼
public class TestDeadLock { private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { new Thread(new Thread1()).start(); new Thread(new Thread2()).start(); } private static class Thread1 implements Runnable { @Override public void run() { synchronized (obj1) { System.out.println("Thread1 拿到了 obj1 的鎖!"); try { // 停頓2秒的意義在於,讓Thread2線程拿到obj2的鎖 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println("Thread1 拿到了 obj2 的鎖!"); } } } } private static class Thread2 implements Runnable { @Override public void run() { synchronized (obj2) { System.out.println("Thread2 拿到了 obj2 的鎖!"); try { // 停頓2秒的意義在於,讓Thread1線程拿到obj1的鎖 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1) { System.out.println("Thread2 拿到了 obj1 的鎖!"); } } } } }
使用jstack加上進程號進行查看