前言
通過上一篇的 JVM 垃圾回收知識,我們了解了 JVM 具體的 垃圾回收算法 和幾種 垃圾回收器。理論是指導實踐的工具,有了理論指導,定位問題的時候,知識和經驗是關鍵基礎,數據可以為我們提供依據。
在線上我們經常會遇見如下幾個問題:
- 內存泄露;
- 某個進程突然
CPU飆升; - 線程死鎖;
- 響應變慢。
如果遇到了以上這種問題,在 線下環境 可以有各種 可視化的本地工具 支持查看。但是一旦到 線上環境,就沒有這么多的 本地調試工具 支持,我們該如何基於 監控工具 來進行定位問題?
我們一般會基於 數據收集 來定位問題,而數據的收集離不開 監控工具 的處理,比如:運行日志、異常堆棧、GC 日志、線程快照、堆內存快照 等。為了解決以上問題,我們常用的 JVM 性能調優監控工具 大致有:jps、jstat、jstack、jmap、jhat、hprof、jinfo。
正文
如果想要查看 Java 進程中 線程堆棧 的信息,可以選擇 jstack 命令。如果要查看 堆內存,可以使用 jmap 導出並使用 jhat 來進行分析,包括查看 類的加載信息,GC 算法,對象 的使用情況等。可以使用 jstat 來對 JVM 進行 統計監測,包括查看各個 區內存 和 GC 的情況,還可以使用 hprof 查看 CPU 使用率,統計 堆內存 使用情況。下面會詳細的介紹這幾個工具的用法。
JVM常見監控工具 & 指令
1. jps進程監控工具
jps 是用於查看有權訪問的 hotspot 虛擬機 的進程。當未指定 hostid 時,默認查看 本機 jvm 進程,否則查看指定的 hostid 機器上的 jvm 進程,此時 hostid 所指機器必須開啟 jstatd 服務。
jps 可以列出 jvm 進程 lvmid,主類類名,main 函數參數, jvm 參數,jar 名稱等信息。
命令格式如下:
1 |
usage: jps [-help] |
參數含義如下:
- -q: 不輸出 類名稱、
Jar名稱 和傳入main方法的 參數; - -l: 輸出
main類或Jar的 全限定名稱; - -m: 輸出傳入
main方法的 參數; - -v: 輸出傳入
JVM的參數。
2. jinfo配置信息查看工具
jinfo(JVM Configuration info)這個命令作用是實時查看和調整 虛擬機運行參數。之前的 jps -v 命令只能查看到顯示 指定的參數,如果想要查看 未顯示 的參數的值就要使用 jinfo 命令。
1 |
Usage: |
參數含義如下:
- pid:本地
jvm服務的進程ID; - executable core:打印 堆棧跟蹤 的核心文件;
- remote server IP/hostname:遠程
debug服務的 主機名 或IP地址; - server id:遠程
debug服務的 進程ID。
參數選項說明如下:
| 參數 | 參數含義 |
|---|---|
| flag | 輸出指定 args 參數的值 |
| flags | 不需要 args 參數,輸出所有 JVM 參數的值 |
| sysprops | 輸出系統屬性,等同於 System.getProperties() |
- 查看正在運行的
jvm進程的 擴展參數。
1 |
$ jinfo -flags 31983 |
- 查看正在運行的
jvm進程的所有 參數信息。
1 |
$ jinfo 31983 |
- 查看正在運行的
jvm進程的 環境變量信息。
1 |
$ jinfo -sysprops 31983 |
2. jstat信息統計監控工具
jstat 是用於識別 虛擬機 各種 運行狀態信息 的命令行工具。它可以顯示 本地 或者 遠程虛擬機 進程中的 類裝載、內存、垃圾收集、jit 編譯 等運行數據,它是 線上 定位 jvm 性能 的首選工具。
jstat 工具提供如下的 jvm 監控功能:
- 類的加載 及 卸載 的情況;
- 查看 新生代、老生代 及 元空間(
MetaSpace)的 容量 及使用情況; - 查看 新生代、老生代 及 元空間(
MetaSpace)的 垃圾回收情況,包括垃圾回收的 次數,垃圾回收所占用的 時間; - 查看 新生代 中
Eden區及Survior區中 容量 及 分配情況 等。
命令格式如下:
1 |
Usage: jstat -help|-options |
參數含義如下:
- option: 參數選項。
- -t: 可以在打印的列加上
timestamp列,用於顯示系統運行的時間。 - -h: 可以在 周期性數據 的時候,可以在指定輸出多少行以后輸出一次 表頭。
- -t: 可以在打印的列加上
- vmid: Virtual Machine ID(進程的
pid)。 - lines: 表頭 與 表頭 的間隔行數。
- interval: 執行每次的 間隔時間,單位為 毫秒。
- count: 用於指定輸出記錄的 次數,缺省則會一直打印。
參數選項說明如下:
- class: 顯示 類加載
ClassLoad的相關信息; - compiler: 顯示
JIT編譯 的相關信息; - gc: 顯示和
gc相關的 堆信息; - gccapacity: 顯示 各個代 的 容量 以及 使用情況;
- gcmetacapacity: 顯示 元空間
metaspace的大小; - gcnew: 顯示 新生代 信息;
- gcnewcapacity: 顯示 新生代大小 和 使用情況;
- gcold: 顯示 老年代 和 永久代 的信息;
- gcoldcapacity: 顯示 老年代 的大小;
- gcutil: 顯示 垃圾回收信息;
- gccause: 顯示 垃圾回收 的相關信息(同
-gcutil),同時顯示 最后一次 或 當前 正在發生的垃圾回收的 誘因; - printcompilation: 輸出
JIT編譯 的方法信息;
2.1. class
顯示和監視 類裝載、卸載數量、總空間 以及 耗費的時間。
1 |
$ jstat -class 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| Loaded | 已經裝載的類的數量 |
| Bytes | 裝載類所占用的字節數 |
| Unloaded | 已經卸載類的數量 |
| Bytes | 卸載類的字節數 |
| Time | 裝載和卸載類所花費的時間 |
2.2. compiler
顯示虛擬機 實時編譯(JIT)的 次數 和 耗時 等信息。
1 |
$ jstat -compiler 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| Compiled | 編譯任務執行數量 |
| Failed | 編譯任務執行失敗數量 |
| Invalid | 編譯任務執行失效數量 |
| Time | 編譯任務消耗時間 |
| FailedType | 最后一個編譯失敗任務的類型 |
| FailedMethod | 最后一個編譯失敗任務所在的類及方法 |
2.3. gc
顯示 垃圾回收(gc)相關的 堆信息,查看 gc 的 次數 及 時間。
1 |
$ jstat -gc 8615 |
比如下面輸出的是 GC 信息,采樣 時間間隔 為 250ms,采樣數為 4:
1 |
$ jstat -gc 8615 250 4 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| S0C | 年輕代中第一個 survivor 的容量 |
| S1C | 年輕代中第二個 survivor 的容量 |
| S0U | 年輕代中第一個 survivor 目前已使用空間 |
| S1U | 年輕代中第二個 survivor 目前已使用空間 |
| EC | 年輕代中 Eden 的容量 |
| EU | 年輕代中 Eden 目前已使用空間 |
| OC | 老年代的容量 |
| OU | 老年代目前已使用空間 |
| MC | 元空間 metaspace 的容量 |
| MU | 元空間 metaspace 目前已使用空間 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| YGCT | 從應用程序啟動到采樣時 年輕代 中 gc 所用時間 |
| FGC | 從應用程序啟動到采樣時 老年代 中 gc 次數 |
| FGCT | 從應用程序啟動到采樣時 老年代 中 gc 所用時間 |
| GCT | 從應用程序啟動到采樣時 gc 用的 總時間 |
2.4. gccapacity
顯示 虛擬機內存 中三代 年輕代(young),老年代(old),元空間(metaspace)對象的使用和占用大小。
1 |
$ jstat -gccapacity 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| NGCMN | 年輕代的 初始化(最小)容量 |
| NGCMX | 年輕代的 最大容量 |
| NGC | 年輕代 當前的容量 |
| S0C | 年輕代中 第一個 survivor 區的容量 |
| S1C | 年輕代中 第二個 survivor 區的容量 |
| EC | 年輕代中 Eden(伊甸園)的容量 |
| OGCMN | 老年代中 初始化(最小)容量 |
| OGCMX | 老年代的 最大容量 |
| OGC | 老年代 當前新生成 的容量 |
| OC | 老年代的容量大小 |
| MCMN | 元空間 的 初始化容量 |
| MCMX | 元空間 的 最大容量 |
| MC | 元空間 當前 新生成 的容量 |
| CCSMN | 最小 壓縮類空間大小 |
| CCSMX | 最大 壓縮類空間大小 |
| CCSC | 當前 壓縮類空間大小 |
| YGC | 從應用程序啟動到采樣時 年輕代 中的 gc 次數 |
| FGC | 從應用程序啟動到采樣時 老年代 中的 gc 次數 |
2.5. gcmetacapacity
顯示 元空間(metaspace)中 對象 的信息及其占用量。
1 |
$ jstat -gcmetacapacity 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| MCMN | 最小 元數據空間容量 |
| MCMX | 最大 元數據空間容量 |
| MC | 當前 元數據空間容量 |
| CCSMN | 最小壓縮 類空間容量 |
| CCSMX | 最大壓縮 類空間容量 |
| CCSC | 當前 壓縮類空間容量 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| FGC | 從應用程序啟動到采樣時 老年代 中 gc 次數 |
| FGCT | 從應用程序啟動到采樣時 老年代 gc 所用時間 |
| GCT | 從應用程序啟動到采樣時 gc 用的 總時間 |
2.6. gcnew
顯示 年輕代對象 的相關信息,包括兩個 survivor 區和 一個 Eden 區。
1 |
$ jstat -gcnew 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| S0C | 年輕代中第一個 survivor 的容量 |
| S1C | 年輕代中第二個 survivor 的容量 |
| S0U | 年輕代中第一個 survivor 目前已使用空間 |
| S1U | 年輕代中第二個 survivor 目前已使用空間 |
| TT | 持有次數限制 |
| MTT | 最大持有次數限制 |
| DSS | 期望的 幸存區 大小 |
| EC | 年輕代中 Eden 的容量 |
| EU | 年輕代中 Eden 目前已使用空間 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| YGCT | 從應用程序啟動到采樣時 年輕代 中 gc 所用時間 |
2.7. gcnewcapacity
查看 年輕代 對象的信息及其占用量。
1 |
$ jstat -gcnewcapacity 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| NGCMN | 年輕代中初始化(最小)的大小 |
| NGCMX | 年輕代的最大容量 |
| NGC | 年輕代中當前的容量 |
| S0CMX | 年輕代中第一個 survivor 的最大容量 |
| S0C | 年輕代中第一個 survivor的容量 |
| S1CMX | 年輕代中第二個 survivor 的最大容量 |
| S1C | 年輕代中第二個 survivor 的容量 |
| ECMX | 年輕代中 Eden 的最大容量 |
| EC | 年輕代中 Eden 的容量 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| FGC | 從應用程序啟動到采樣時 老年代 中 gc 次數 |
2.8. gcold
顯示 老年代對象 的相關信息。
1 |
$ jstat -gcold 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| MC | 元空間(metaspace)的容量 |
| MU | 元空間(metaspace)目前已使用空間 |
| CCSC | 壓縮類空間大小 |
| CCSU | 壓縮類空間 使用 大小 |
| OC | 老年代 的容量 |
| OU | 老年代 目前已使用空間 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| FGC | 從應用程序啟動到采樣時 老年代 中 gc 次數 |
| FGCT | 從應用程序啟動到采樣時 老年代 gc 所用時間 |
| GCT | 從應用程序啟動到采樣時 gc 用的 總時間 |
2.9. gcoldcapacity
查看 老年代 對象的信息及其占用量。
1 |
$ jstat -gcoldcapacity 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| OGCMN | 老年代 中初始化(最小)的大小 |
| OGCMX | 老年代 的最大容量 |
| OGC | 老年代 當前新生成的容量 |
| OC | 老年代 的容量 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 的次數 |
| FGC | 從應用程序啟動到采樣時 老年代 中 gc 的次數 |
| FGCT | 從應用程序啟動到采樣時 老年代 中 gc 所用時間 |
| GCT | 從應用程序啟動到采樣時 gc 用的 總時間 |
2.10. gcutil
顯示 垃圾回收(gc)過程中的信息,包括各個 內存的使用占比,垃圾回收 時間 和回收 次數。
1 |
$ jstat -gcutil 8615 |
參數列表及含義如下:
| 參數 | 參數含義 |
|---|---|
| S0 | 年輕代中 第一個 survivor 區 已使用 的占當前容量百分比 |
| S1 | 年輕代中 第二個 survivor 區 已使用 的占當前容量百分比 |
| E | 年輕代中 Eden 區 已使用 的占當前容量百分比 |
| O | 老年代 中 已使用 的占當前容量百分比 |
| M | 元空間(metaspace)中 已使用 的占當前容量百分比 |
| YGC | 從應用程序啟動到采樣時 年輕代 中 gc 次數 |
| YGCT | 從應用程序啟動到采樣時 年輕代 中 gc 所用時間 |
| FGC | 從應用程序啟動到采樣時 老年代 gc 次數 |
| FGCT | 從應用程序啟動到采樣時 老年代 gc 所用時間 |
| GCT | 從應用程序啟動到采樣時 gc 用的 總時間 |
3. jmap堆內存統計工具
jmap (JVM Memory Map) 命令用來查看 堆內存 使用狀況,一般結合 jhat 使用,用於生成 heap dump 文件。jmap 不僅能生成 dump 文件,還可以查詢 finalize 執行隊列、Java 堆 和 元空間 metaspace 的詳細信息,如當前 使用率、當前使用的是哪種 收集器 等等。
如果不使用這個命令,還可以使用
-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候,自動生成dump文件。
命令格式如下:
1 |
Usage: |
參數含義如下:
- pid:本地
jvm服務的進程ID; - executable core:打印 堆棧跟蹤 的核心文件;
- remote server IP/hostname:遠程
debug服務的 主機名 或IP地址; - server id:遠程
debug服務的 進程ID。
參數選項說明如下:
| 參數 | 參數含義 |
|---|---|
| heap | 顯示 堆 中的摘要信息 |
| histo | 顯示 堆 中對象的統計信息 |
| histo[:live] | 只顯示 堆 中 存活對象 的統計信息 |
| clstats | 顯示 類加載 的統計信息 |
| finalizerinfo | 顯示在 F-Queue 隊列 等待 Finalizer 線程執行 finalizer 方法的對象 |
| dump | 導出內存轉儲快照 |
注意:
dump內存快照分析基本上包含了histo、clstats、finalizerinfo等功能。
3.1. heap
顯示 堆 中的摘要信息。包括 堆內存 的使用情況,正在使用的 GC 算法、堆配置參數 和 各代中堆內存 使用情況。可以用此來判斷內存目前的 使用情況 以及 垃圾回收 情況。
1 |
$ jmap -heap 11368 |
這里主要對 heap configuration 的參數列表說明一下:
| 參數 | 對應啟動參數 | 參數含義 |
|---|---|---|
| MinHeapFreeRatio | -XX:MinHeapFreeRatio | JVM堆最小空閑比率(default 40) |
| MaxHeapFreeRatio | -XX:MaxHeapFreeRatio | JVM堆最大空閑比率(default 70) |
| MaxHeapSize | XX:Xmx | JVM堆的最大大小 |
| NewSize | -XX:NewSize | JVM堆新生代的默認(初始化)大小 |
| MaxNewSize | -XX:MaxNewSize | JVM堆新生代的最大大小 |
| OldSize | -XX:OldSize | JVM堆老年代的默認(初始化)大小 |
| NewRatio | -XX:NewRatio | JVM堆新生代和老年代的大小比例 |
| SurvivorRatio | -XX:SurvivorRatio | JVM堆年輕代中Eden區與Survivor區的大小比值 |
| MetaspaceSize | -XX:MetaspaceSize | JVM元空間(metaspace)初始化大小 |
| MaxMetaspaceSize | -XX:MaxMetaspaceSize | JVM元空間(metaspace)最大大小 |
| CompressedClass SpaceSize | -XX:CompressedClass SpaceSize | JVM類指針壓縮空間大小, 默認為1G |
| G1HeapRegionSize | -XX:G1HeapRegionSize | 使用G1垃圾回收器時單個Region的大小,取值為1M至32M |
3.2. histo
打印堆的 對象統計,包括 對象實例數、內存大小 等等。因為在 histo:live 前會進行 full gc,如果帶上 live 則只統計 活對象。不加 live 的堆大小要大於加 live 堆的大小。
1 |
$ jmap -histo:live 12498 |
其中,class name 是 對象類型,對象 縮寫類型 與 真實類型 的對應說明如下:
| 對象縮寫類型 | 對象真實類型 |
|---|---|
| B | byte |
| C | char |
| D | double |
| F | float |
| I | int |
| J | long |
| Z | boolean |
| [ | 數組,如[I表示int[] |
| [L+類名 | 其他對象 |
3.3. dump
dump 用於導出內存轉儲快照。常用的方式是通過 jmap 把進程 內存使用情況 dump 到文件中,再用 jhat 分析查看。jmap 進行 dump 的命令格式如下:
1 |
jmap -dump:format=b,file=dumpFileName |
參數含義如下:
| 參數 | 參數含義 |
|---|---|
| dump | 堆到文件 |
| format | 指定輸出格式 |
| live | 指明是活着的對象 |
| file | 指定文件名 |
- 通過
jmap導出 內存快照,文件命名為dump.dat:
1 |
jmap -dump:format=b,file=dump.dat 12498 |
導出的 dump 文件可以通過 MAT、VisualVM 和 jhat 等工具查看分析,后面會詳細介紹。
4. jhat堆快照分析工具
jhat(JVM Heap Analysis Tool)命令通常與 jmap 搭配使用,用來分析 jmap 生成的 dump。jhat 內置了一個微型的 HTTP/HTML 服務器,生成 dump 的分析結果后,可以在瀏覽器中查看。
注意:一般不會直接在 服務器 上 進行分析,因為使用
jhat是一個 耗時 並且 耗費硬件資源 的過程,一般的做法是,把 服務器 生成的dump文件復制到 本地 或 其他機器 上進行分析。
命令格式如下:
1 |
Usage: jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file> |
參數含義如下:
| 參數 | 參數值默認值 | 參數含義 |
|---|---|---|
| stack | true | 關閉 對象分配調用棧跟蹤。如果分配位置信息在堆轉儲中不可用。則必須將此標志設置為false。 |
| refs | true | 關閉 對象引用跟蹤。默認情況下,返回的指針是指向其他特定對象的對象。如 反向鏈接 或 輸入引用,會統計/計算堆中的所有對象 |
| port | 7000 | 設置jhat HTTP server的端口號 |
| exclude | — | 指定對象查詢時需要排除的數據成員列表文件 |
| baseline | — | 指定一個 基准堆轉儲。在兩個heap dumps中有相同object ID的對象時,會被標記為不是新的,其他對象被標記為新的。在比較兩個不同的堆轉儲時很有用 |
| debug | 0 | 設置debug級別,0表示不輸出調試信息。值越大則表示輸出更詳細的debug信息 |
| version | — | 啟動后只顯示版本信息就退出 |
| J | — | jhat命令實際上會啟動一個JVM來執行,通過-J可以在啟動JVM時傳入一些 啟動參數。例如, -J-Xmx512m則指定運行jhat 的Java虛擬機使用的最大堆內存為512MB。 |
- 前面提到,通過
jmap dump出來的文件可以用MAT、VisualVM等工具查看,這里我們用jhat查看:
1 |
$ jhat -port 7000 dump.dat |
- 打開瀏覽器,輸入
http://localhost:7000,查看jhat的分析報表頁面:
- 可以按照 包名稱 查看項目模塊中的具體 對象示例:
除此之外,報表分析的最后一頁,還提供了一些擴展查詢:
- 顯示所有的
Root集合; - 顯示所有
class的當前 對象實例數量(包含JVM平台相關類); - 顯示所有
class的當前 對象實例數量(除去JVM平台相關類); - 顯示 堆內存 中實例對象的 統計直方圖(和直接使用
jmap沒有區別); - 顯示
finalizer虛擬機 二次回收 的信息摘要; - 執行
jhat提供的 對象查詢語言(OQL)獲取指定對象的實例信息。
注意:
jhat支持根據某些條件來 過濾 或 查詢 堆的對象。可以在jhat的html頁面中執行OQL語句,來查詢符合條件的對象。OQL`具體的語法可以直接訪問 http://localhost:7000/oqlhelp。
在具體排查時,需要結合代碼,觀察是否 大量應該被回收 的對象 一直被引用,或者是否有 占用內存特別大 的對象 無法被回收。
5. jstack堆棧跟蹤工具
jstack 用於生成 java 虛擬機當前時刻的 線程快照。線程快照 是當前 java 虛擬機內 每一條線程 正在執行的 方法堆棧 的 集合。生成線程快照的主要目的是定位線程出現 長時間停頓 的原因,如 線程間死鎖、死循環、請求外部資源 導致的 長時間等待 等等。
線程出現 停頓 的時候,通過 jstack 來查看 各個線程 的 調用堆棧,就可以知道沒有響應的線程到底在后台做什么事情,或者等待什么資源。如果 java 程序 崩潰 生成 core 文件,jstack 工具可以通過 core 文件獲取 java stack 和 native stack 的信息,從而定位程序崩潰的原因。
命令格式如下:
1 |
Usage: |
參數含義如下:
- pid:本地
jvm服務的進程ID; - executable core:打印 堆棧跟蹤 的核心文件;
- remote server IP/hostname:遠程
debug服務的 主機名 或IP地址; - server id:遠程
debug服務的 進程ID。
參數選項說明如下:
| 參數 | 參數含義 |
|---|---|
| F | 當正常輸出請求 不被響應 時,強制輸出 線程堆棧 |
| l | 除堆棧外,顯示關於 鎖的附加信息 |
| m | 如果調用到 本地方法 的話,可以顯示 C/C++ 的堆棧 |
注意:在實際運行中,往往一次
dump的信息,還不足以確認問題。建議產生三次dump信息,如果每次dump都指向同一個問題,才能確定問題的典型性。
5.1. 系統線程狀態
在 dump 文件里,值得關注的 線程狀態 有:
- 死鎖:Deadlock(重點關注)
- 執行中:Runnable
- 等待資源:Waiting on condition(重點關注)
- 等待獲取監視器:Waiting on monitor entry(重點關注)
- 暫停:Suspended
- 對象等待中:Object.wait() 或 TIMED_WAITING
- 阻塞:Blocked(重點關注)
- 停止:Parked
具體的含義如下所示:
(a). Deadlock
死鎖線程,一般指多個線程調用期間發生 資源的相互占用,導致一直等待無法釋放的情況。
(b). Runnable
一般指該線程正在 執行狀態 中,該線程占用了 資源,正在 處理某個請求。有可能正在傳遞
SQL到數據庫執行,有可能在對某個文件操作,有可能進行數據類型等轉換。
(c). Waiting on condition
該狀態在線程等待 某個條件 的發生。具體是什么原因,可以結合
stacktrace來分析。線程處於這種 等待狀態,一旦有數據准備好讀之后,線程會重新激活,讀取並處理數據。
線程正處於等待資源或等待某個條件的發生,具體的原因需要結合下面堆棧信息進行分析。
-
如果 堆棧信息 明確是 應用代碼,則證明該線程正在 等待資源。一般是大量 讀取某種資源 且該資源采用了 資源鎖 的情況下,線程進入 等待狀態。
-
如果發現有 大量的線程 都正處於這種狀態,並且堆棧信息中得知正在 等待網絡讀寫,這是因為 網絡阻塞 導致 線程無法執行,很有可能是一個 網絡瓶頸 的征兆:
- 網絡非常 繁忙,幾乎消耗了所有的帶寬,仍然有大量數據等待網絡讀寫;
- 網絡可能是 空閑的,但由於 路由 或 防火牆 等原因,導致包無法正常到達。
-
還有一種常見的情況是該線程在
sleep,等待sleep的時間到了,將被喚醒。
(d). Locked
線程阻塞,是指當前線程執行過程中,所需要的資源 長時間等待 卻 一直未能獲取到,被容器的線程管理器標識為 阻塞狀態,可以理解為 等待資源超時 的線程。
(e). Waiting for monitor entry 和 in Object.wait()
Monitor是Java中實現線程之間的 互斥與協作 的主要手段,它可以看成是 對象 或者Class的 鎖。每一個對象都有一個monitor。
5.1. 死鎖示例
下面給出一個 死鎖 的案例,在 IntLock 中定義了兩個靜態的 可重入鎖 實例,在主方法中聲明了 兩個線程 對 兩把鎖 進行資源競爭。
1 |
public class DeadLockRunner { |
5.2. dump日志分析
啟動 DeadLockRunner 的 main() 方法,使用 jps 查看阻塞的 jvm 進程的 id,然后使用 jstack 查看 線程堆棧信息,可以發現兩個線程相互 競爭資源,出現死鎖。
1 |
$ jstack -l 15584 |
參考
周志明,深入理解Java虛擬機:JVM高級特性與最佳實踐,機械工業出版社
歡迎關注技術公眾號:零壹技術棧
本帳號將持續分享后端技術干貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分布式和微服務,架構學習和進階等學習資料和文章。




