入門科普,圍繞JVM的各種外掛技術[轉]

jstat, jmap, btrace, jprofiler, vjtools都基於什麼實現? 對圍繞JVM的各種工具的外掛技術,運用大整理術,讓大家從茫然,到輕搖紙扇,知道分子。

歸攏一下,就是C 和 Java兩種Agent,SA 和 VirtualMachine 兩種 Attach,JMX和PerfData兩種Data,兩兩之間很是混淆,網上好像少了篇簡明扼要的科普,所以自己操刀補了一篇,作為上週的直播《VJTools如何利用佛性技術完全JVM》的續集。


1. 兩種Agent

1.1 Native Agent

以 C/C++代碼編寫的Agent,用強大的JVMTI(JVM Tool Interface)接口與JVM進行通訊,訂閱感興趣的JVM事件(比如方法出入、線程始末等等),當這些事件發生時,會回調Agent的代碼。 JVMTI 同時提供了眾多的功能函數,查詢和控制 Java 應用的運行狀態,包括內存控制和對象獲取,線程與鎖等等,簡直無所不能。

使用者,包括各種Profile工具,如Yourkit,JProfiler,Aysnc-Profiler, 還有動態Reload Class而不重啟應用的JRebel。

使用方式 ,可以在啟動命令里加入 -agentlib: 或 -agentpath: /path/to/agent.so,也可以用後面講的VituralMachine.attach()動態加載。

1.2 Java Agent

Java Agent的底層也是JVMTI ,但後門能力就只剩一個AOP 代碼植入了:在加載class文件之前做攔截並對字節碼做修改。比如AspectJ,單元測試覆蓋率的Jacoco,動態重載Class的Spring-Loaded。

典型代碼如下:


public class MyAgent {
public static void premain(String args, Instrumentation instrumentation){
ClassFileTransformer transformer = new MyClassWeaving();
instrumentation.addTransformer(transformer);
}
}


另一種逍遙的用法,就是為了隨時讓 “一段代碼” 與 “主應用” 在同一JVM中運行,而不用修改主應用的代碼去顯式調用。

比如JMX的agent,啟動一條TCP偵聽線程響應JMX 請求。比如jolokia的agent,啟動一條Http偵聽線程,響應Restful版的JMX請求。比如btrace,啟動一條TCP線程與btrace client通信,接收client發過來的腳本字節碼,進行加載並輸出結果。

它有兩種啟動方式:

一種是在啟動時命令行加入 -javaagent:/path/to/agent.jar,根據agent.jar中的MANIFEST.MF文件中的Premain-Class定義,JVM找到相應的MyAgent類,調用其premain函數。

一種是通過後面講的VM.attach()技術,在任意時刻由外部程序來靈活加載,調用其agentmain函數。

2.兩種Attach

兩種截然不同方式實現的Attach,本質上都是在跟蹤程序與目標JVM之間建立一個溝通的管道,然後在跟蹤程序使用特定的API去操作目標JVM。

2.1 Vitural Machine.attach()

跟蹤程序通過Unix Domain Socket 與目標JVM的Attach Listener線程進行交互。 Socket 文件為/tmp/.java_pid$PID。

API 接口是com.sun.tools.attach.VirtualMachine 及其子類sun.tools.attach.HotSpotVirtualMachine,在tools.jar中,運行時需要依賴,可以做下面的事情:

● dumpHeap: jmap -dump 效果● heapHisto: jmap -histo效果● threadDump: jstack效果● dataDump: kill-3 效果(jstack + jmap -heap)● loadAgent: 動態加載C/Java Agent● agentProperties: 獲得已加載Agent的屬性● sytemProperties: 獲得System Properties● setFlag: 動態設置可寫的JVM參數(但沒幾個是可寫的)● printFlag: 打印JVM 參數的值● jcmd: 執行jcmd命令,具體能幹啥見jcmd $PID help

可見, jmap, jmap,jcmd 們默認就是基於這個機制來做事情的。

VJTools的vjmxcli的特色之一,如果應用的啟動腳本忘了設定啟動JMX,可以根據PID attach到應用裡,動態的把JMX Agent啟動起來,然後通Agent屬性獲得本地連接地址,嗯, jconsole也是這麼幹的。

2.2 SA.attach()

著名的SA(Serviceability Agent),用於分析JVM運行時進程的Snapshot數據。Snapshot的意思,就是當SA 開始分析時,整個目標JVM是停頓下來不工作的,讓SA可以從容讀取進程內存中的數據,直到斷開後才會恢復。所以在生產上使用這類工具時,必須先摘除流量。

這個神奇的操作,主要是通過系統調用ptrace實現。ptrace會使內核暫停目標進程並將控制權交給跟蹤進程,使跟蹤進程得以察看目標進程的內存,詳見ptrace的man,所以在容器環境下,需要打開ptrace的安全權限。

API的接口,一個是 sun.jvm.hotspot.HotSpotAgent 負責attach, sun.jvm.hotspot.runtime.VM負責操作,在sa-jdi.jar中。

VM類從內存二進制信息中,提取出JVM內部數據結構,包括:

● 內存的getObjectHeap()/getUniverse()● 處線程的getThreads()● 永久代內容的getSymbolTable(),getStringTable(), getSystemDictionary()● 還有很厲害的讀內部Native對象值的getTypeDataBase()

jstack,jmap 們默認用前面的VM.attach()模式,與目標JVM的Attach Listener線程通信, 但如果目標JVM已經半死不活,Attach Listener線程無力響應請求時,就可以增加-F 參數,轉而使用SA.attach 模式,用ptrace去暴力接管進程, 詳細代碼見sun.tools.jmap.JMap。看代碼你還會發現,因為AttachListener支持的命令有限,所以jmap -heap 打印heap的總結信息時,也是以SA模式進去。

VJTools的vjmap是jmap的一個增強,能夠獨立各個分代中對象的數量大小統計,用以排除新生代對象的干擾,查找內存緩慢洩漏的問題,比如更直接找出Survior區裡age>2的對象,裡面就使用了VM類的觀察者模式回調,和直接內存訪問 兩種方法來遍歷。

SA模式比VM模式做相同事情時要慢一截,非必需時不要用它。還有,如果跟蹤程序被kill-9 非正常退出,沒有執行中斷SA,目標JVM就會一直暫停在那裡,Linux下可以執行kill -18 $PID 發送SIGCONT信號重新激活目標進程。

3. 兩種Data

3.1 JMX

文章已太多,不再囉嗦。其中vjtools的vjtop,如何不停頓JVM的獲得線程的CPU、內存信息,獲得某條繁忙進程的StackTrace看看它在忙些什麼,值得一看。

3.2 PerfData

很多人不知道的一個機制,JVM其實每秒都會將自己的大量統計數據,寫入到 /tmp/hsperfdata_$username/$pid 文件中。

用下面指令可以感受下:

jcmd $PID PerfCounter.print

內容包括jvm的基本信息,內存,GC,線程數等等,還有一些JMX中沒有暴露的數據,比如包含JVM中所有的停頓的SafePoint信息。

//線程的情況
java.threads.daemon=6
java.threads.live=7
...
// younggen的情況
sun.gc.generation.0.capacity=44695552
sun.gc.generation.0.maxCapacity=715784192
sun.gc.generation.0.minCapacity=44695552
....

jps,其實就是讀取/tmp/hsperfdata_$username/ 目錄下所有的文件。

jstat,同樣是讀取這個神秘的文件。一個很大的好處,就是它只默默讀取文件,而不會像JMX那樣要與應用程序交互,打擾應用程序的工作。

自己寫代碼也簡單,使用 sun.management.counter.perf.PerfInstrumentation類即可,VJTools裡的vjtop 從JMX,PerfData和/proc/pid 三處地方,綜合打印JVM的概況和繁忙線程的情況,裡面的PerfData類就是其使用的展示。

PerfData文件是mmap到內存中的,讀寫都很快,但每次寫完還要更新磁盤上的文件元數據比如last modified time,如果遇上磁盤高IO,還是有概率造成JVM被鎖定一段時間。所以我們以前通過-XX:+PerfDisableSharedMem禁止了perfdata的寫入,不過現在又有點搖擺。

注意,perfdata 和 vm.attach 都需要在/tmp 目錄讀寫文件,如果目標JVM的啟動參數重新指定了臨時目錄,而跟蹤程序依然去讀取/tmp 目錄,也會導致這些機制失效。

4. 小結

VJTools (https://github.com/vipshop/vjtools) 的三個工具,是上面的兩種Attach, 兩種Data的使用樣本,大家可以直接閱讀源碼來加深印象,再順手點個Star。

有關的...

  • 2018-08-06 -- 快速,低成本,低擾動地運行一段Java代碼
  • 2018-07-25 -- 《唯品會Java開發手冊》-與阿里手冊的比較文學
  • 2018-07-20 -- 關鍵業務系統的JVM參數推薦(2018仲夏版)
  • 2018-06-06 -- 唯品會Java核心項目VJTools開源了



原文地址:http://calvin1978.blogcn.com/articles/vjtools-tools4.html


分享到:


相關文章: