天天看點

深入淺出JProfiler

該文章來自阿裡巴巴技術協會(ata)

由于最近工作的原因,使用了jprofiler(8)來做性能瓶頸分析,發現這個工具使用起來确實挺友善,現在整理一下jprofiler相關知識(在google查了一通,沒有我想要的)。

jprofiler是由ej-technologies gmbh公司開發的一款性能瓶頸分析工具(該公司還開發部署工具)。

其特點:

使用友善

界面操作友好

對被分析的應用影響小

cpu,thread,memory分析功能尤其強大

支援對jdbc,nosql, jsp, servlet, socket等進行分析

支援多種模式(離線,線上)的分析

跨平台 

深入淺出JProfiler

 (圖1)

q1. jprofiler既然是一款性能瓶頸分析工具,這些分析的相關資料來自于哪裡?

q2. jprofiler是怎麼将這些資料收集并展現的?

深入淺出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提供了幫助向導.

深入淺出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:

深入淺出JProfiler

filter: 什麼class需要被分析。分為包含和不包含兩種類型的filter。

深入淺出JProfiler

(圖4)

profiling settings: 收據收集的政策:sampling和 instrumentation,一些資料采集細節可以自定義.

深入淺出JProfiler

(圖5)

triggers: 一般用于**offline**模式,告知jprofiler agent 什麼時候觸發什麼行為來收集指定資訊.

深入淺出JProfiler

(圖6)

live memory: class/class instance的相關資訊。 例如對象的個數,大小,對象建立的方法執行棧,對象建立的熱點。

深入淺出JProfiler

(圖7)

heap walker: 對一定時間内收集的記憶體對像資訊進行靜态分析,功能強大且使用。包含對象的outgoing reference, incoming reference, biggest object等

深入淺出JProfiler

(圖8)

cpu views: cpu消耗的分布及時間(cpu時間或者運作時間); 方法的執行圖; 方法的執行統計(最大,最小,平均運作時間等)

深入淺出JProfiler

(圖9)

thread: 目前jvm所有線程的運作狀态,線程持有鎖的狀态,可dump線程。

深入淺出JProfiler

(圖10)

monitors & locks: 所有線程持有鎖的情況以及鎖的資訊

深入淺出JProfiler

(圖11)

telemetries: 包含heap, thread, gc, class等的趨勢圖(遙測視圖)

為了友善實踐,直接以jprofiler8自帶的一個例子來幫助了解上面的相關概念。

jprofiler 自帶的例子如下:模拟了記憶體洩露和線程阻塞的場景:

具體源碼參考: /jprofiler install path/demo/bezier

深入淺出JProfiler

(圖12 )

深入淺出JProfiler

(圖13 leak memory 模拟記憶體洩露, simulate blocking 模拟線程間鎖的阻塞)

a1. 首先來分析下記憶體洩露的場景:(勾選圖13中 leak memory 模拟記憶體洩露)

1. 在**telemetries-> memory**視圖中你會看到大緻如下圖的場景(在看的過程中可以間隔一段時間去執行run gc這個功能):看到下圖藍色區域,老生代在gc後(**波谷**)記憶體的大小在慢慢的增加(理想情況下,這個值應該是穩定的)

深入淺出JProfiler

(圖14)

在 live memory->recorded objects 中點選**record allocation data**按鈕,開始統計一段時間内建立的對象資訊。執行一次**run gc**後看看目前對象資訊的大小,并點選工具欄中**mark current**按鈕(其實就是給目前對象數量打個标記。執行一次run gc,然後再繼續觀察;執行一次run gc,然後再繼續觀察...。最後看看哪些對象在不斷gc後,數量還一直上漲的。最後你看到的資訊可能和下圖類似

深入淺出JProfiler

(圖15 綠色是标記前的數量,紅色是标記後的增量)

在heap walker中分析剛才記錄的對象資訊

深入淺出JProfiler

(圖16)

深入淺出JProfiler

(圖17)

點選上圖中執行個體最多的class,右鍵**use selected instances->reference->incoming reference**.

發現該long資料最終是存放在**bezier.beaieranim.leakmap**中。

深入淺出JProfiler

(圖18)

在allocations tab項中,右鍵點選其中的某個方法,可檢視到具體的源碼資訊.

深入淺出JProfiler

(圖19)

【注】:到這裡問題已經非常清楚了,明白了在圖17中為什麼哪些執行個體的數量是一樣多,并且為什麼記憶體在fullgc後還是回收不了(一個old 區的對象leakmap,put的資訊也會進入old區, leakmap如回收不掉,那麼該map中包含的對象也回收不掉)。

a2. 模拟線程阻塞的場景(勾選圖13中simulate blocking 模拟線程間鎖的阻塞)

為了友善區分線程,我将demo中的bezieranim.java的l236的線程命名為test

正常情況下,如下圖

深入淺出JProfiler

(圖20)

勾選了demo中"simulate blocking"選項後,如下圖(注意看下下圖中的狀态圖示), test線程block狀态明顯增加了。

深入淺出JProfiler

(圖21)

在**monitors & locks->monitor history**觀察了一段時間後,會發現有4種發生鎖的情況。

第一種:

awt-eventqueue-0 線程持有一個object的鎖,并且處于waiting狀态。

圖下方的代碼提示出demo.block方法調用了object.wait方法。這個還是比較容易了解的。 

深入淺出JProfiler

(圖22)

第二種:

awt-eventqueue-0占有了bezier.bezieranim$demo執行個體上的鎖,而test線程等待該線程釋放。

注意下圖中下方的源代碼, 這種鎖的出現原因是demo的blcok方法在awt和test線程

都會被執行,并且該方法是synchronized.

深入淺出JProfiler

(圖23)

第三種和第四種:

test線程中會不斷向事件event dispatching thread送出任務,導緻競争java.awt.eventqueue對象鎖。

送出任務的方式是下面的代碼:<code>repaint()</code>和<code>eventqueue.invokelater</code>

深入淺出JProfiler

(圖24)

jprofiler都會對一些特殊操作給予提示,這時候最好仔細閱讀下說明.

"mark current"功能在某些場景很有效

heap walker一般是靜态分析在live memory-&gt;recorder objects的對象資訊,這些資訊可能會被gc回收掉,導緻heap walker中什麼也沒有顯示出來。這種現象是正常的。

可以才工具欄中start recordings配置一次性收集的資訊

filter中include和exclude是有順序的,注意使用下圖**左下方**的**“show filter tree”**來驗證一下順序 

深入淺出JProfiler

 (圖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