一.JProfiler是什麼
JProfiler是由ej-technologies GmbH公司開發的一款性能瓶頸分析工具(該公司還開發部署工具)。
其特點:
- 使用友善
- 界面操作友好
- 對被分析的應用影響小
- CPU,Thread,Memory分析功能尤其強大
- 支援對jdbc,noSql, jsp, servlet, socket等進行分析
- 支援多種模式(離線,線上)的分析
- 跨平台 (圖1)
JProfiler使用說明
二.資料采集
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)JProfiler使用說明 -
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使用說明
四. JProfiler核心概念
- Filter: 什麼class需要被分析。分為包含和不包含兩種類型的Filter。 (圖4)
JProfiler使用說明 - Profiling Settings: 收據收集的政策:Sampling和 Instrumentation,一些資料采集細節可以自定義. (圖5)
JProfiler使用說明 - Triggers: 一般用于**offline**模式,告知JProfiler Agent 什麼時候觸發什麼行為來收集指定資訊. (圖6)
JProfiler使用說明 - Live memory: class/class instance的相關資訊。 例如對象的個數,大小,對象建立的方法執行棧,對象建立的熱點。 (圖7)
JProfiler使用說明 - Heap walker: 對一定時間内收集的記憶體對像資訊進行靜态分析,功能強大且使用。包含對象的outgoing reference, incoming reference, biggest object等 (圖8)
JProfiler使用說明 - CPU views: CPU消耗的分布及時間(cpu時間或者運作時間); 方法的執行圖; 方法的執行統計(最大,最小,平均運作時間等) (圖9)
JProfiler使用說明 - Thread: 目前jvm所有線程的運作狀态,線程持有鎖的狀态,可dump線程。 (圖10)
JProfiler使用說明 - Monitors & locks: 所有線程持有鎖的情況以及鎖的資訊 (圖11)
JProfiler使用說明 - 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 綠色是标記前的數量,紅色是标記後的增量)
JProfiler使用說明 - 在Heap walker中分析剛才記錄的對象資訊 (圖16)
JProfiler使用說明 (圖17)JProfiler使用說明 -
點選上圖中執行個體最多的class,右鍵**Use Selected Instances->Reference->Incoming Reference**.
發現該Long資料最終是存放在**bezier.BeaierAnim.leakMap**中。
(圖18)JProfiler使用說明
在Allocations tab項中,右鍵點選其中的某個方法,可檢視到具體的源碼資訊.
(圖19)
【注】:到這裡問題已經非常清楚了,明白了在圖17中為什麼哪些執行個體的數量是一樣多,并且為什麼記憶體在fullgc後還是回收不了(一個old 區的對象leakMap,put的資訊也會進入old區, leakMap如回收不掉,那麼該map中包含的對象也回收不掉)。
A2. 模拟線程阻塞的場景(勾選圖13中Simulate blocking 模拟線程間鎖的阻塞)
為了友善區分線程,我将Demo中的BezierAnim.java的L236的線程命名為test
public void start() {
thread = new Thread(this, "test");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
}
正常情況下,如下圖
(圖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對象鎖。
送出任務的方式是下面的代碼:
repaint()
和
EventQueue.invokeLater
public void run() {
Thread me = Thread.currentThread();
while (thread == me) {
repaint();
if (block) {
block(false);
}
try {
Thread.sleep();
} catch (Exception e) {
break;
}
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
onEDTMethod();
}
});
}
thread = null;
}
(圖24)
六. 最佳實踐
- JProfiler都會對一些特殊操作給予提示,這時候最好仔細閱讀下說明.
- "Mark Current"功能在某些場景很有效
- Heap walker一般是靜态分析在Live memory->Recorder objects的對象資訊,這些資訊可能會被GC回收掉,導緻Heap walker中什麼也沒有顯示出來。這種現象是正常的。
- 可以才工具欄中Start Recordings配置一次性收集的資訊
- Filter中include和exclude是有順序的,注意使用下圖**左下方**的**“Show Filter Tree”**來驗證一下順序 (圖25)
JProfiler使用說明
七. 參考文獻
- 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的具體功能進行介紹,部分内容參照自部落格:JProfiler 解決 Java 伺服器的性能跟蹤,如果讀者英文閱讀能力比較強也可以在工具欄上邊點選Help,直接閱讀英文幫助,下文部分内容亦是參照自英文API。
3.2.1 記憶體視圖 Memory Views
JProfiler的記憶體視圖部分可以提供動态的記憶體使用狀況更新視圖和顯示關于記憶體配置設定狀況資訊的視圖。所有的視圖都有幾個聚集層并且能夠顯示現有存在的對象和作為垃圾回收的對象。
-
所有對象 All Objects
顯示類或在狀況統計和尺碼資訊堆上所有對象的包。你可以标記目前值并顯示差異值。
-
記錄對象 Record Objects
顯示類或所有已記錄對象的包。你可以标記出目前值并且顯示差異值。
-
配置設定通路樹 Allocation Call Tree
顯示一棵請求樹或者方法、類、包或對已選擇類有帶注釋的配置設定資訊的J2EE元件。
-
配置設定熱點 Allocation Hot Spots
顯示一個清單,包括方法、類、包或配置設定已選類的J2EE元件。你可以标注目前值并且顯示差異值。對于每個熱點都可以顯示它的跟蹤記錄樹。
-
類追蹤器 Class Tracker
類跟蹤視圖可以包含任意數量的圖表,顯示標明的類和包的執行個體與時間。
3.2.2 堆周遊 Heap Walker
在JProfiler的堆周遊器(Heap Walker)中,你可以對堆的狀況進行快照并且可以通過選擇步驟下尋找感興趣的對象。堆周遊器有五個視圖:
-
類 Classes
顯示所有類和它們的執行個體,可以右擊具體的類"Used Selected Instance"實作進一步跟蹤。
-
配置設定 Allocations
為所有記錄對象顯示配置設定樹和配置設定熱點。
-
索引 References
為單個對象和“顯示到垃圾回收根目錄的路徑”提供索引圖的顯示功能。還能提供合并輸入視圖和輸出視圖的功能。
-
時間 Time
顯示一個對已記錄對象的解決時間的柱狀圖。
-
檢查 Inspections
顯示了一個數量的操作,将分析目前對象集在某種條件下的子集,實質是一個篩選的過程。
-
圖表 Graph
你需要在references視圖和biggest視圖手動添加對象到圖表,它可以顯示對象的傳入和傳出引用,能友善的找到垃圾收集器根源。
tips:在工具欄點選"Go To Start"可以使堆記憶體重新計數,也就是回到初始狀态。
3.2.3 CPU 視圖 CPU Views
JProfiler 提供不同的方法來記錄通路樹以優化性能和細節。線程或者線程組以及線程狀況可以被所有的視圖選擇。所有的視圖都可以聚集到方法、類、包或J2EE元件等不同層上。CPU視圖部分包括:
-
通路樹 Call Tree
顯示一個積累的自頂向下的樹,樹中包含所有在JVM中已記錄的通路隊列。JDBC,JMS和JNDI服務請求都被注釋在請求樹中。請求樹可以根據Servlet和JSP對URL的不同需要進行拆分。
-
熱點 Hot Spots
顯示消耗時間最多的方法的清單。對每個熱點都能夠顯示回溯樹。該熱點可以按照方法請求,JDBC,JMS和JNDI服務請求以及按照URL請求來進行計算。
-
通路圖 Call Graph
顯示一個從已選方法、類、包或J2EE元件開始的通路隊列的圖。
-
方法統計 Method Statistis
顯示一段時間内記錄的方法的調用時間細節。
3.2.4 線程視圖 Thread Views
JProfiler通過對線程曆史的監控判斷其運作狀态,并監控是否有線程阻塞産生,還能将一個線程所管理的方法以樹狀形式呈現。對線程剖析,JProfiler提供以下視圖:
-
線程曆史 Thread History
顯示一個與線程活動和線程狀态在一起的活動時間表。
-
線程監控 Thread Monitor
顯示一個清單,包括所有的活動線程以及它們目前的活動狀況。
-
線程轉儲 Thread Dumps
顯示所有線程的堆棧跟蹤。
3.2.5 監控器視圖 Monitor Views
JProfiler提供了不同的監控器視圖,如下所示:
-
目前鎖定圖表 Current Locking Graph
顯示JVM中的目前鎖定情況。
-
目前螢幕 Current Monitors
顯示目前正在等待或阻塞中的線程操作。
-
鎖定曆史圖表 Locking History Graph
顯示記錄在JVM中的鎖定曆史。
-
監控器曆史 Monitor History
顯示等待或者阻塞的曆史。
-
監控器使用統計 Monitor Usage Statistics
計算統計監控器監控的資料。
3.2.6 VM遙感勘測技術視圖 VM Telemetry Views
觀察JVM的内部狀态,JProfiler提供了不同的遙感勘測視圖,如下所示:
-
記憶體 Memory
顯示堆棧的使用狀況和堆棧尺寸大小活動時間表。
-
記錄的對象 Recorded Objects
顯示一張關于活動對象與數組的圖表的活動時間表。
-
記錄的生産量 Recorded Throughput
顯示一段時間累計的JVM生産和釋放的活動時間表。
-
垃圾回收活動 GC Activity
顯示一張關于垃圾回收活動的活動時間表。
-
類 Classes
顯示一個與已裝載類的圖表的活動時間表。
-
線程 Threads
顯示一個與動态線程圖表的活動時間表。
-
CPU負載 CPU Load
顯示一段時間中CPU的負載圖表。
3.3 使用心得
3.3.1 看記憶體圖
Key 1,把目光集中到上圖的藍色部分,可以發現每次記憶體上升到一個峰值之後便會下跌,這個動作其實就是GC在回收記憶體,并且上升和回收的幅度大抵相同,如果你發現記憶體圖中可用記憶體随着時間的流逝一直在上升而沒有GC回收的動作,那麼你就要懷疑它是否存在着記憶體洩露了;
Key 2,當你懷疑記憶體洩露時,可以在Memory Views視圖Mark Current Values,然後在一段時間後F4,找出那個沒有被釋放掉的異常類對其進行深一步的追蹤;
Key 3,追蹤主要使用Heap Walker的各項功能,各功能具體能做何種分析請複習章節3.1直覺認識和3.2.2 堆周遊 Heap Walker。