當服務發生GC問題時,一般會使用jmap工具進行分析,jmap工具很強大,所以有必要了解它的方方面面。
jmap可以做什麼?
1、jmap -histo[:live]
通過histo選項,打印當前java堆中各個對象的數量、大小。
如果添加了live,只會打印活躍的對象。
2、jmap -dump:[live,]format=b,file=<filename>
通過-dump選項,把java堆中的對象dump到本地文件,然後使用MAT進行分析。
如果添加了live,只會dump活躍的對象。
3、jmap -heap
通過-heap選項,打印java堆的配置情況和使用情況,還有使用的GC算法。
4、jmap -finalizerinfo
通過-finalizerinfo選項,打印那些正在等待執行finalize方法的對象。
5、jmap -permstat
通過-permstat選項,打印java堆永久代的信息,包括class loader相關的信息,和interned Strings的信息。
jmap實現原理
通過jmap和jvm之間進行通信,有兩種實現方式:attach 和 SA。
attach
attach方式,簡單來說就是客戶端和服務端之間的通信,客戶端發送請求,主要邏輯在服務端執行,jmap相當於客戶端,JVM相當於服務端。
在JVM中,有一個叫”Attach Listener”的線程,專門負責監聽attach的請求,並執行對應的操作。
比如現在執行”jmap -histo:live 5409″,一步一步的實現如下:
- 在Jmap.java類的main函數中,對參數進行解析。
- 解析出來參數中有“-histo:live”,則執行histo方法:
attach方法建立了jmap進程和JVM之間的socket連接,建立過程可以查看笨神的文章JVM Attach機制實現,後續基於該連接進行通信。
因為命令行中添加了[:live]選項,這裡的live參數是true。
再看看heapHisto方法
executeCommand方法基於之前的socket連接向JVM發送了一條”inspectheap”命令,當然了,還有參數。
虛擬機的”Attach Listener”線程當發現有新的命令時,就拿出來處理它。
命令和具體的函數對應關係如下:
和”inspectheap”對應的是heap_inspection方法,實現如下:
live_objects_only的值取決於請求中是否有”-live”,再jmap中,取決於是否有”:live”,所以不管是不是添加了”:live”,都會有STW過程,時間長短而已。
在VM_GC_HeapInspection的doit方法中
_full_gc的值就是live_objects_only,如果為true,可能會執行一次full gc,清空非活躍的對象,但是可能會因為GC locker,導致跳過本次的GC。
“jmap -dump”實現的原理和”jmap -histo”類似,都是通過attach的方式實現,
attach API的實現方式是:
- 客戶端連接到目標JVM,向其發出一個類似“inspectheap”命令;
- 目標JVM接收到命令,執行JVM內相關函數,將收集到的結果以文本形式返回;
- 客戶端接收到返回的文本並將其顯示出來;
SA
假如執行”jmap -heap 5409″,就不會使用attach方式實現了。
在參數解析中,如果參數是”-heap|-heap:format=b|-permstat|-finalizerinfo”中的一種,或者添加了”-F”,比如”jmap -histo -F 5409″,則使用SA的方式。
SA方式,和attach方式不同的是,相關的主要邏輯都在SA中實現,從JVM中獲取數據即可。
可以大概看下”jmap -heap”的實現,對應的實現類是”HeapSummary”,內部通過BugSpotAgent工具類attach到目標VM,更具體的底層細節,可以參考HotSpot Serviceability Agent 實現淺析
執行jmap -heap有些時候可能會導致進程變T,一般是有一個線程在等信號量,這時會block住其它所有線程,可以執行kill -CONT
閱讀更多 戚小柒說IT 的文章