效能診斷利器JProfiler快速入門和最佳實踐

吳波bruce_wu發表於2019-01-06

背景

效能診斷是軟體工程師在日常工作中需要經常面對和解決的問題,在使用者體驗至上的今天,解決好應用的效能問題能帶來非常大的收益。Java 作為最流行的程式語言之一,其應用效能診斷一直受到業界廣泛關注。可能造成 Java 應用出現效能問題的因素非常多,例如執行緒控制、磁碟讀寫、資料庫訪問、網路I/O、垃圾收集等。想要定位這些問題,一款優秀的效能診斷工具必不可少。本文將介紹 Java 效能診斷過程中的常用工具,並重點介紹其中的優秀代表 JProfiler 的基本原理和最佳實踐(本文所作的調研基於jprofiler10.1.4)。

Java 效能診斷工具簡介

在 Java 的世界裡,有許多診斷工具可供選擇,既包括像 jmap、jstat 這樣的簡單命令列工具,又包括 JVisualvm、JProfiler 等圖形化綜合診斷工具,同時還有 SkyWalking、ARMS 這樣的針對分散式應用的效能監控系統。下面分別對其進行介紹。

簡單命令列工具

JDK 內建了許多命令列工具,它們可用來獲取目標 JVM 不同方面、不同層次的資訊。

  • jinfo – 用於實時檢視和調整目標 JVM 的各項引數。
  • jstack – 用於獲取目標 Java 程式內的執行緒堆疊資訊,可用來檢測死鎖、定位死迴圈等。
  • jmap – 用於獲取目標 Java 程式的記憶體相關資訊,包括 Java 堆各區域的使用情況、堆中物件的統計資訊、類載入資訊等。
  • jstat – 一款輕量級多功能監控工具,可用於獲取目標 Java 程式的類載入、JIT 編譯、垃圾收集、記憶體使用等資訊。
  • jcmd – 相比 jstat 功能更為全面的工具,可用於獲取目標 Java 程式的效能統計、JFR、記憶體使用、垃圾收集、執行緒堆疊、JVM 執行時間等資訊。

圖形化綜合診斷工具

使用上述命令列工具或組合能幫您獲取目標 Java 應用效能相關的基礎資訊,但它們存在下列侷限:

  1. 無法獲取方法級別的分析資料,如方法間的呼叫關係、各方法的呼叫次數和呼叫時間等(這對定位應用效能瓶頸至關重要)。
  2. 要求使用者登入到目標 Java 應用所在的宿主機上,使用起來不是很方便。
  3. 分析資料通過終端輸出,結果展示不夠直觀。

下面介紹幾款圖形化的綜合效能診斷工具。

JVisualvm

JVisualvm 是 JDK 內建的視覺化效能診斷工具,它通過 JMX、jstatd、Attach API 等方式獲取目標 JVM 的分析資料,包括 CPU 使用率、記憶體使用量、執行緒堆疊資訊等。此外,它還能直觀地展示 Java 堆中各物件的數量和大小、各 Java 方法的呼叫次數和執行時間等。

JProfiler

JProfiler 是由 ej-technologies 公司開發的一款 Java 應用效能診斷工具。它聚焦於四個重要主題上。

  1. 方法呼叫 – 對方法呼叫的分析可以幫助您瞭解應用程式正在做什麼,並找到提高其效能的方法。
  2. 記憶體分配 – 通過分析堆上物件、引用鏈和垃圾收集能幫您修復記憶體洩漏問題,優化記憶體使用。
  3. 執行緒和鎖 – JProfiler 提供多種針對執行緒和鎖的分析檢視助您發現多執行緒問題。
  4. 高階子系統 – 許多效能問題都發生在更高的語義級別上。例如,對於JDBC呼叫,您可能希望找出執行最慢的 SQL 語句。JProfiler 支援對這些子系統進行整合分析。

分散式應用效能診斷

如果只需要診斷單機 Java 應用的效能瓶頸,上面介紹的診斷工具就已經夠用了。但隨著現代系統架構逐漸從單體轉變為分散式、微服務,單純使用上述工具往往無法滿足需求,這時就需要藉助 JaegerARMSSkyWalking 這些分散式追蹤系統提供的全鏈路追蹤功能。分散式追蹤系統種類繁多,但實現原理都大同小異,它們通過程式碼埋點的方式記錄 tracing 資訊,通過 SDK 或 agent 將記錄的資料傳輸至中央處理系統,最後提供 query 介面對結果進行展示和分析,想了解更多分散式追蹤系統的原理可參考文章開放分散式追蹤(OpenTracing)入門與 Jaeger 實現

JProfiler 簡介

核心元件

JProfiler 包含用於採集目標 JVM 分析資料的 JProfiler agent、用於視覺化分析資料的 JProfiler UI、提供各種功能的命令列工具,它們之間的關係如下圖所示。

jprofiler

JProfiler agent

JProfiler agent 是一個本地庫,它可以在 JVM 啟動時通過引數-agentpath:<path to native library>進行載入或者在程式執行時通過 JVM Attach 機制進行載入。Agent 被成功載入後,會設定 JVMTI 環境,監聽虛擬機器產生的事件,如類載入、執行緒建立等。例如,當它監聽到類載入事件後,會給這些類注入用於執行度量操作的位元組碼。

JProfiler UI

JProfiler UI 是一個可獨立部署的元件,它通過 socket 和 agent 建立連線。這意味著不論目標 JVM 執行在本地還是遠端,JProfiler UI 和 agent 間的通訊機制都是一樣的。

JProfiler UI 的主要功能是展示通過 agent 採集上來的分析資料,此外還可以通過它控制 agent 的採集行為,將快照儲存至磁碟,展示儲存的快照。

命令列工具

JProfiler 提供了一系列命令列工具以實現不同的功能。

  • jpcontroller – 用於控制 agent 的採集行為。它通過 agent 註冊的 JProfiler MBean 向 agent 傳遞命令。
  • jpenable – 用於將 agent 載入到一個正在執行的 JVM 上。
  • jpdump – 用於獲取正在執行的 JVM 的堆快照。
  • jpexport & jpcompare – 用於從儲存的快照中提取資料並建立 HTML 報告。

安裝配置

JProfiler 同時支援診斷本地和遠端 Java 應用的效能。如果您需要實時採集並展示遠端 JVM 的分析資料,需要完成以步驟:

  1. 在本地安裝 JProfiler UI。
  2. 在遠端宿主機上安裝 JProfiler agent 並讓其被目標 JVM 載入。
  3. 配置 UI 到 agent 的連線。

具體步驟可參考文件 Installing JProfilerProfiling A JVM

最佳實踐

本章將以高效能寫 LogHub 類庫 Aliyun LOG Java Producer 為原型,帶您瞭解如何使用 JProfiler 剖析它的效能。如果您的應用或者您在使用 producer 的過程中遇到了效能問題,也可以用類似的方式定位問題根因。如果您還不瞭解 producer 的功能,建議先閱讀文章日誌上雲利器 – Aliyun LOG Java Producer。本章使用的樣例程式碼參見 SamplePerformance.java

JProfiler 設定

資料採集模式

JProfier 提供兩種資料採集模式 Sampling 和 Instrumentation。

  • Sampling – 適合於不要求資料完全精確的場景。優點是對系統效能的影響較小,缺點是某些特性不支援(如方法級別的統計資訊)。
  • Instrumentation – 完整功能模式,統計資訊也是精確的。缺點是如果需要分析的類比較多,對應用效能影響較大。為了降低影響,往往需要和 Filter 一起使用。

由於我們需要獲取方法級別的統計資訊,這裡選擇了 Instrumentation 模式。同時配置了 Filter,讓 agent 只記錄位於 Java 包com.aliyun.openservices.aliyun.log.producer下的類和類com.aliyun.openservices.log.Client的 CPU 分析資料。

應用啟動模式

通過為 JProfiler agent 指定不同的引數可以控制應用的啟動模式。

  • 等待模式 – 只有在 Jprofiler GUI 和 agent 建立連線並完成分析配置設定後,應用才會真正啟動。在這種模式下,您能夠獲取應用啟動時期的分析資料。對應的命令為-agentpath:<path to native library>=port=8849
  • 立即啟動模式 – 應用會立即啟動,Jprofiler GUI 會在需要時和 agent 建立連線並設定分析配置。這種模式相對靈活,但會丟失應用啟動初期的分析資料。對應的命令為-agentpath:<path to native library>=port=8849,nowait
  • 離線模式 – 通過觸發器記錄資料、儲存快照供事後分析。對應的命令為-agentpath:<path to native library>=offline,id=xxx,config=/config.xml

因為是在測試環境,同時對應用啟動初期的效能也比較關注,這裡選擇了預設的等待模式。

使用 JProfiler 診斷效能

在完成 JProfiler 的設定後,便可以對 Producer 的效能進行診斷。

Overview

在概覽頁我們可以清晰的看到記憶體使用量、垃圾收集活動、類載入數量、執行緒個數和狀態、CPU 使用率等指標隨時間變化的趨勢。

overview

通過此圖,我們可以作出如下基本判斷:

  1. 程式在執行過程中會產生大量物件,但這些物件生命週期極短,大部分都能被垃圾收集器及時回收,不會造成記憶體無限增長。
  2. 載入類的數量在程式初始時增長較快,隨後保持平穩,符合預期。
  3. 在程式執行過程中,有大量執行緒處於阻塞狀態,需要重點關注。
  4. 在程式剛啟動時,CPU 使用率較高,需要進一步探究其原因。

CPU views

CPU views 下的各個子檢視展示了應用中各方法的執行次數、執行時間、呼叫關係等資訊,能幫我們定位對應用效能影響最大的方法。

Call Tree

Call tree 通過樹形圖清晰地展現了方法間的層次呼叫關係。同時,JProfiler 將子方法按照它們的執行總時間由大到小排序,這能讓您快速定位關鍵方法。

call_tree

對於 Producer 而言,方法SendProducerBatchTask.run()耗時最多,繼續向下檢視會發現該方法的主要時間消耗在了執行方法Client.PutLogs()上。

Hot Spots

如果您的應用方法很多,且很多子方法的執行時間比較接近,使用 hot spots 檢視往往能助您更快地定位問題。該檢視能根據方法的單獨執行時間、總執行時間、平均執行時間、呼叫次數等屬性對它們排序。其中,單獨執行時間等於該方法的總執行時間減去所有子方法的總執行時間。

hot_spots

在該檢視下,可以看到Client.PutLogs()LogGroup.toByteArray()SamplePerformance$1.run()是單獨執行時間耗時最多的三個方法。

Call Graph

找到了關鍵方法後,call graph 檢視能為您呈現與該方法直接關聯的所有方法。這有助於我們對症下藥,制定合適的效能優化策略。

call_graph

這裡,我們觀察到方法Client.PutLogs()執行的主要時間花費在了物件序列化上,因此效能優化的關鍵是提供執行效率更高的序列化方法。

Live memory

Live memory 下的各個子檢視能讓您掌握記憶體的具體分配和使用情況,助您判斷是否存在記憶體洩漏問題。

All Objects

All Objects 檢視展示了當前堆中各種物件的數量和總大小。由圖可知,程式在執行過程中構造出了大量 LogContent 物件。

all_Objects

Allocation Call Tree

Allocation Call Tree 以樹形圖的形式展示了各方法分配的記憶體大小。可以看到,SamplePerformance$1.run()SendProducerBatchTask.run()是記憶體分配大戶。

allocation_call_tree

Allocation Hot Spots

如果方法比較多,您還可以通過 Allocation Hot Spots 檢視快速找出分配物件最多的方法。

allocation_hot_spots

Thread History

執行緒歷史記錄檢視直觀地展示了各執行緒在不同時間點的狀態。

thread_history

不同執行緒執行的任務不同,所展現的狀態特徵也不同。

  • 執行緒pool-1-thread-<M>會迴圈呼叫producer.send()方法非同步傳送資料,它們在程式剛啟動時一直處於執行狀態,但隨後在大部分時間裡處於阻塞狀態。這是因為 producer 傳送資料的速率低於資料的產生速率,且單個 producer 例項能快取的資料大小有限。在程式執行初始,producer 有足夠空間快取待傳送資料,所以pool-1-thread-<M>一直處於執行狀態,這也就解釋了為何程式在剛啟動時 CPU 使用率較高。隨著時間的推移,producer 的快取被逐漸耗盡,pool-1-thread-<M>必須等到 producer “釋放”出足夠的空間才有機會繼續執行,這也是為什麼我們會觀察到大量執行緒處於阻塞狀態。
  • aliyun-log-producer-0-mover負責將超時 batch 投遞到傳送執行緒池中。由於傳送速率較快,batch 會因快取的資料達到了上限被pool-1-thread-<M>直接投遞到傳送執行緒池中,因此 mover 執行緒在大部分時間裡都處於等待狀態。
  • aliyun-log-producer-0-io-thread-<N>作為真正執行資料傳送任務的執行緒有一部分時間花在了網路 I/O 狀態。
  • aliyun-log-producer-0-success-batch-handler用於處理髮送成功的 batch。由於回撥函式比較簡單,執行時間短,它在大部分時間裡都處於等待狀態。
  • aliyun-log-producer-0-failure-batch-handler用於處理髮送失敗的 batch。由於沒有資料傳送失敗,它一直處於等待狀態。

通過上述分析可知,這些執行緒的狀態特徵都是符合預期的。

Overhead Hot Spots Detected

當程式執行結束後,JProfiler 會彈出一個對話方塊展示那些頻繁被呼叫,但執行時間又很短的方法。在下次診斷時,您可以讓 JProfiler agent 在分析過程中忽略掉這些方法以減輕對應用效能的影響。

overhead_hot_spots_detected

小結

通過 JProfiler 的診斷可知應用不存在大的效能問題,也不存在記憶體洩漏。下一步的優化方向是提升物件的序列化效率。

參考資料


相關文章