性能诊断利器 JProfiler 快速入门和最佳实践
背景
性能診斷是軟件工程師在日常工作中需要經常面對和解決的問題,在用戶體驗至上的今天,解決好應用的性能問題能帶來非常大的收益。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 應用性能相關的基礎信息,但它們存在下列局限:
下面介紹幾款圖形化的綜合性能診斷工具。
JVisualvm
JVisualvm?是 JDK 內置的可視化性能診斷工具,它通過 JMX、jstatd、Attach API 等方式獲取目標 JVM 的分析數據,包括 CPU 使用率、內存使用量、線程堆棧信息等。此外,它還能直觀地展示 Java 堆中各對象的數量和大小、各 Java 方法的調用次數和執行時間等。
JProfiler
JProfiler?是由 ej-technologies 公司開發的一款 Java 應用性能診斷工具。它聚焦于四個重要主題上。
分布式應用性能診斷
如果只需要診斷單機 Java 應用的性能瓶頸,上面介紹的診斷工具就已經夠用了。但隨著現代系統架構逐漸從單體轉變為分布式、微服務,單純使用上述工具往往無法滿足需求,這時就需要借助?Jaeger、ARMS、SkyWalking?這些分布式追蹤系統提供的全鏈路追蹤功能。分布式追蹤系統種類繁多,但實現原理都大同小異,它們通過代碼埋點的方式記錄 tracing 信息,通過 SDK 或 agent 將記錄的數據傳輸至中央處理系統,最后提供 query 接口對結果進行展示和分析,想了解更多分布式追蹤系統的原理可參考文章開放分布式追蹤(OpenTracing)入門與 Jaeger 實現。
JProfiler 簡介
核心組件
JProfiler 包含用于采集目標 JVM 分析數據的 JProfiler agent、用于可視化分析數據的 JProfiler UI、提供各種功能的命令行工具,它們之間的關系如下圖所示。
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 的分析數據,需要完成以步驟:
具體步驟可參考文檔?Installing JProfiler?和?Profiling 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 使用率等指標隨時間變化的趨勢。
通過此圖,我們可以作出如下基本判斷:
CPU views
CPU views 下的各個子視圖展示了應用中各方法的執行次數、執行時間、調用關系等信息,能幫我們定位對應用性能影響最大的方法。
Call Tree
Call tree 通過樹形圖清晰地展現了方法間的層次調用關系。同時,JProfiler 將子方法按照它們的執行總時間由大到小排序,這能讓您快速定位關鍵方法。
對于 Producer 而言,方法SendProducerBatchTask.run()耗時最多,繼續向下查看會發現該方法的主要時間消耗在了執行方法Client.PutLogs()上。
Hot Spots
如果您的應用方法很多,且很多子方法的執行時間比較接近,使用 hot spots 視圖往往能助您更快地定位問題。該視圖能根據方法的單獨執行時間、總執行時間、平均執行時間、調用次數等屬性對它們排序。其中,單獨執行時間等于該方法的總執行時間減去所有子方法的總執行時間。
在該視圖下,可以看到Client.PutLogs(),LogGroup.toByteArray(),SamplePerformance$1.run()是單獨執行時間耗時最多的三個方法。
Call Graph
找到了關鍵方法后,call graph 視圖能為您呈現與該方法直接關聯的所有方法。這有助于我們對癥下藥,制定合適的性能優化策略。
這里,我們觀察到方法Client.PutLogs()執行的主要時間花費在了對象序列化上,因此性能優化的關鍵是提供執行效率更高的序列化方法。
Live memory
Live memory 下的各個子視圖能讓您掌握內存的具體分配和使用情況,助您判斷是否存在內存泄漏問題。
All Objects
All Objects 視圖展示了當前堆中各種對象的數量和總大小。由圖可知,程序在運行過程中構造出了大量 LogContent 對象。
Allocation Call Tree
Allocation Call Tree 以樹形圖的形式展示了各方法分配的內存大小。可以看到,SamplePerformance$1.run()和SendProducerBatchTask.run()是內存分配大戶。
Allocation Hot Spots
如果方法比較多,您還可以通過 Allocation Hot Spots 視圖快速找出分配對象最多的方法。
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 在分析過程中忽略掉這些方法以減輕對應用性能的影響。
小結
通過 JProfiler 的診斷可知應用不存在大的性能問題,也不存在內存泄漏。下一步的優化方向是提升對象的序列化效率。
?
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的性能诊断利器 JProfiler 快速入门和最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《2018年云上挖矿态势分析报告》发布,
- 下一篇: 我和 Spring 大神的一天