該文章來自阿裡巴巴技術協會(ata)
由于最近工作的原因,使用了jprofiler(8)來做性能瓶頸分析,發現這個工具使用起來确實挺友善,現在整理一下jprofiler相關知識(在google查了一通,沒有我想要的)。
jprofiler是由ej-technologies gmbh公司開發的一款性能瓶頸分析工具(該公司還開發部署工具)。
其特點:
使用友善
界面操作友好
對被分析的應用影響小
cpu,thread,memory分析功能尤其強大
支援對jdbc,nosql, jsp, servlet, socket等進行分析
支援多種模式(離線,線上)的分析
跨平台
(圖1)
q1. jprofiler既然是一款性能瓶頸分析工具,這些分析的相關資料來自于哪裡?
q2. jprofiler是怎麼将這些資料收集并展現的?
(圖2)
a1. 分析的資料主要來自于下面倆部分
1. 一部分來自于jvm的分析接口**jvmti**(jvm tool interface) , jdk必須>=1.6
jvmti is an event-based system. the profiling agent library can register handler functions for different events. it can then enable or disable selected events
例如: 對象的生命周期,thread的生命周期等資訊
2. 一部分來自于instruments classes(可了解為class的重寫,增加jprofiler相關統計功能)
例如:方法執行時間,次數,方法棧等資訊
a2. 資料收集的原理如圖2
1. 使用者在jprofiler gui中下達監控的指令(一般就是點選某個按鈕)
2. jprofiler gui jvm 通過socket(預設端口8849),發送指令給被分析的jvm中的jprofile agent。
3. jprofiler agent(如果不清楚agent請看文章第三部分"啟動模式") 收到指令後,将該指令轉換成相關需要監聽的事件或者指令,來注冊到jvmti上或者直接讓jvmti去執行某功能(例如dump jvm記憶體)
4. jvmti 根據注冊的事件,來收集目前jvm的相關資訊。 例如: 線程的生命周期; jvm的生命周期;classes的生命周期;對象執行個體的生命周期;堆記憶體的實時資訊等等
5. jprofiler agent将采集好的資訊儲存到**記憶體**中,按照一定規則統計好(如果發送所有資料jprofiler gui,會對被分析的應用網絡産生比較大的影響)
6. 傳回給jprofiler gui socket.
7. jprofiler gui socket 将收到的資訊傳回 jprofiler gui render
8. jprofiler gui render 渲染成最終的展示效果
a1. jprofier采集方式分為兩種:sampling(樣本采集)和instrumentation
sampling: 類似于樣本統計, 每隔一定時間(5ms)将每個線程棧中方法棧中的資訊統計出來。優點是對應用影響小(即使你不配置任何filter, filter可參考文章第四部分),缺點是一些資料/特性不能提供(例如:方法的調用次數)
instrumentation: 在class加載之前,jprofier把相關功能代碼寫入到需要分析的class中,對正在運作的jvm有一定影響。優點: 功能強大,但如果需要分析的class多,那麼對應用影響較大,一般配合filter一起使用。是以一般jre class和framework的class是在filter中通常會過濾掉。
注: jprofiler本身沒有指出資料的采集類型,這裡的采集類型是針對方法調用的采集類型 。因為jprofiler的絕大多數核心功能都依賴方法調用采集的資料, 是以可以直接認為是jprofiler的資料采集類型。
a2: 啟動模式:
attach mode
可直接将本機正在運作的jvm加載jprofiler agent. 優點是很友善,缺點是一些特性不能支援。如果選擇instrumentation資料采集方式,那麼需要花一些額外時間來重寫需要分析的class。
profile at startup
在被分析的jvm啟動時,将指定的jprofiler agent手動加載到該jvm。jprofiler gui 将收集資訊類型和政策等配置資訊通過socket發送給jprofiler agent,收到這些資訊後該jvm才會啟動。
在被分析的jvm 的啟動參數增加下面内容:
文法: -agentpath:[path to jprofilerti library]
【注】: 文法不清楚沒關系,jprofiler提供了幫助向導.
(圖3)
prepare for profiling:
和profile at startup的主要差別:被分析的jvm不需要收到jprofiler gui 的相關配置資訊就可以啟動。
offline profiling
一般用于适用于不能直接調試線上的場景。offline profiling需要将資訊采集内容和政策(一些trigger, trigger請參考文章第五部分)打包成一個配置檔案(config.xml),線上上啟動該jvm 加載 jprofiler agent時,加載該xml。那麼jprofiler agent會根據trigger的類型會生成不同的資訊。例如: heap dump; thread dump; method call record等
文法:
-agentpath:/home/2080/jprofiler8/bin/linux-x64/libjprofilerti.so=offline,id=151,config=/home/2080/config.xml
【注】: config.xml中的每一個被分析的jvm的采集資訊都有一個id來辨別。
下面是使用了離線模式,并使用了每隔一秒dump heap 的trigger:
filter: 什麼class需要被分析。分為包含和不包含兩種類型的filter。
(圖4)
profiling settings: 收據收集的政策:sampling和 instrumentation,一些資料采集細節可以自定義.
(圖5)
triggers: 一般用于**offline**模式,告知jprofiler agent 什麼時候觸發什麼行為來收集指定資訊.
(圖6)
live memory: class/class instance的相關資訊。 例如對象的個數,大小,對象建立的方法執行棧,對象建立的熱點。
(圖7)
heap walker: 對一定時間内收集的記憶體對像資訊進行靜态分析,功能強大且使用。包含對象的outgoing reference, incoming reference, biggest object等
(圖8)
cpu views: cpu消耗的分布及時間(cpu時間或者運作時間); 方法的執行圖; 方法的執行統計(最大,最小,平均運作時間等)
(圖9)
thread: 目前jvm所有線程的運作狀态,線程持有鎖的狀态,可dump線程。
(圖10)
monitors & locks: 所有線程持有鎖的情況以及鎖的資訊
(圖11)
telemetries: 包含heap, thread, gc, class等的趨勢圖(遙測視圖)
為了友善實踐,直接以jprofiler8自帶的一個例子來幫助了解上面的相關概念。
jprofiler 自帶的例子如下:模拟了記憶體洩露和線程阻塞的場景:
具體源碼參考: /jprofiler install path/demo/bezier
(圖12 )
(圖13 leak memory 模拟記憶體洩露, simulate blocking 模拟線程間鎖的阻塞)
a1. 首先來分析下記憶體洩露的場景:(勾選圖13中 leak memory 模拟記憶體洩露)
1. 在**telemetries-> memory**視圖中你會看到大緻如下圖的場景(在看的過程中可以間隔一段時間去執行run gc這個功能):看到下圖藍色區域,老生代在gc後(**波谷**)記憶體的大小在慢慢的增加(理想情況下,這個值應該是穩定的)
(圖14)
在 live memory->recorded objects 中點選**record allocation data**按鈕,開始統計一段時間内建立的對象資訊。執行一次**run gc**後看看目前對象資訊的大小,并點選工具欄中**mark current**按鈕(其實就是給目前對象數量打個标記。執行一次run gc,然後再繼續觀察;執行一次run gc,然後再繼續觀察...。最後看看哪些對象在不斷gc後,數量還一直上漲的。最後你看到的資訊可能和下圖類似
(圖15 綠色是标記前的數量,紅色是标記後的增量)
在heap walker中分析剛才記錄的對象資訊
(圖16)
(圖17)
點選上圖中執行個體最多的class,右鍵**use selected instances->reference->incoming reference**.
發現該long資料最終是存放在**bezier.beaieranim.leakmap**中。
(圖18)
在allocations tab項中,右鍵點選其中的某個方法,可檢視到具體的源碼資訊.
(圖19)
【注】:到這裡問題已經非常清楚了,明白了在圖17中為什麼哪些執行個體的數量是一樣多,并且為什麼記憶體在fullgc後還是回收不了(一個old 區的對象leakmap,put的資訊也會進入old區, leakmap如回收不掉,那麼該map中包含的對象也回收不掉)。
a2. 模拟線程阻塞的場景(勾選圖13中simulate blocking 模拟線程間鎖的阻塞)
為了友善區分線程,我将demo中的bezieranim.java的l236的線程命名為test
正常情況下,如下圖
(圖20)
勾選了demo中"simulate blocking"選項後,如下圖(注意看下下圖中的狀态圖示), test線程block狀态明顯增加了。
(圖21)
在**monitors & locks->monitor history**觀察了一段時間後,會發現有4種發生鎖的情況。
第一種:
awt-eventqueue-0 線程持有一個object的鎖,并且處于waiting狀态。
圖下方的代碼提示出demo.block方法調用了object.wait方法。這個還是比較容易了解的。
(圖22)
第二種:
awt-eventqueue-0占有了bezier.bezieranim$demo執行個體上的鎖,而test線程等待該線程釋放。
注意下圖中下方的源代碼, 這種鎖的出現原因是demo的blcok方法在awt和test線程
都會被執行,并且該方法是synchronized.
(圖23)
第三種和第四種:
test線程中會不斷向事件event dispatching thread送出任務,導緻競争java.awt.eventqueue對象鎖。
送出任務的方式是下面的代碼:<code>repaint()</code>和<code>eventqueue.invokelater</code>
(圖24)
jprofiler都會對一些特殊操作給予提示,這時候最好仔細閱讀下說明.
"mark current"功能在某些場景很有效
heap walker一般是靜态分析在live memory->recorder objects的對象資訊,這些資訊可能會被gc回收掉,導緻heap walker中什麼也沒有顯示出來。這種現象是正常的。
可以才工具欄中start recordings配置一次性收集的資訊
filter中include和exclude是有順序的,注意使用下圖**左下方**的**“show filter tree”**來驗證一下順序
(圖25)
jprofiler helper: http://resources.ej-technologies.com/jprofiler/help/doc/index.html
jvmti: http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html
如果上面的描述有錯誤或者不清晰的地方,歡迎斧正。
另外補充一句:jprofiler是收費的
https://oss.aliyuncs.com/yqfiles/84ff55814581391bb8f4248ca9d9264e.ppt