轉載:https://blog.csdn.net/lmb55/article/details/79267277java
1、概述算法
開發大型 Java 應用程序的過程當中不免遇到內存泄露、性能瓶頸等問題,好比文件、網絡、數據庫的鏈接未釋放,未優化的算法等。隨着應用程序的持續運行,可能會形成整個系統運行效率降低,嚴重的則會形成系統崩潰。爲了找出程序中隱藏的這些問題,在項目開發後期每每會使用性能分析工具來對應用程序的性能進行分析和優化。sql
VisualVM 是一款免費的性能分析工具。它經過 jvmstat、JMX、SA(Serviceability Agent)以及 Attach API 等多種方式從程序運行時得到實時數據,從而進行動態的性能分析。同時,它能自動選擇更快更輕量級的技術儘可能減小性能分析對應用程序形成的影響,提升性能分析的精度。數據庫
本文將對 VisualVM 的主要功能逐一介紹並探討如何利用得到的數據進行性能分析及調優。IBM開發文檔緩存
2、背景知識:性能分析的主要方式tomcat
1.監視:監視是一種用來查看應用程序運行時行爲的通常方法。一般會有多個視圖(View)分別實時地顯示 CPU 使用狀況、內存使用狀況、線程狀態以及其餘一些有用的信息,以便用戶能很快地發現問題的關鍵所在。安全
2.轉儲:性能分析工具從內存中得到當前狀態數據並存儲到文件用於靜態的性能分析。Java 程序是經過在啓動 Java 程序時添加適當的條件參數來觸發轉儲操做的。它包括如下三種:ruby
系統轉儲:JVM 生成的本地系統的轉儲,又稱做核心轉儲。通常的,系統轉儲數據量大,須要平臺相關的工具去分析,如 Windows 上的 windbg 和 Linux 上的 gdb.
Java 轉儲:JVM 內部生成的格式化後的數據,包括線程信息,類的加載信息以及堆的統計數據。一般也用於檢測死鎖。
堆轉儲:JVM 將全部對象的堆內容存儲到文件。服務器
3.快照:應用程序啓動後,性能分析工具開始收集各類運行時數據,其中一些數據直接顯示在監視視圖中,而另外大部分數據被保存在內部,直到用戶要求獲取快照,基於這些保存的數據的統計信息才被顯示出來。快照包含了應用程序在一段時間內的執行信息,一般有 CPU 快照和內存快照兩種類型。網絡
CPU 快照:主要包含了應用程序中函數的調用關係及運行時間,這些信息一般能夠在 CPU 快照視圖中進行查看。
內存快照:主要包含了內存的分配和使用狀況、載入的全部類、存在的對象信息及對象間的引用關係等。這些信息一般能夠在內存快照視圖中進行查看。
4.性能分析:性能分析是經過收集程序運行時的執行數據來幫助開發人員定位程序須要被優化的部分,從而提升程序的運行速度或是內存使用效率,主要有如下三個方面:
CPU 性能分析:CPU 性能分析的主要目的是統計函數的調用狀況及執行時間,或者更簡單的狀況就是統計應用程序的 CPU 使用狀況。一般有 CPU 監視和 CPU 快照兩種方式來顯示 CPU 性能分析結果。
內存性能分析:內存性能分析的主要目的是經過統計內存使用狀況檢測可能存在的內存泄露問題及肯定優化內存使用的方向。一般有內存監視和內存快照兩種方式來顯示內存性能分析結果。
線程性能分析:線程性能分析主要用於在多線程應用程序中肯定內存的問題所在。通常包括線程的狀態變化狀況,死鎖狀況和某個線程在線程生命期內狀態的分佈狀況等
3、VisualVM 安裝
一、VisualVM 安裝
VisualVM 是一個性能分析工具,自從 JDK 6 Update 7 之後已經做爲 Oracle JDK 的一部分,位於 JDK 根目錄的 bin 文件夾下。VisualVM 自身要在 JDK6 以上的版本上運行,可是它可以監控 JDK1.4 以上版本的應用程序。
VisualVM可使用JDK自帶的jvisualvm(bin目錄下)也能夠單獨下載,通過實驗,發現安裝後的JDK中自帶的jvisualvm包含的插件比較少大概有五六個左右,單獨下載的插件包含的比較多大概有24個左右。它也能夠監控1.6之前的JDK,可是對某些模塊支持的並非很好,沒法顯示。
二、安裝 VisualVM 上的插件
VisualVM 插件中心提供不少插件以供安裝向 VisualVM 添加功能。能夠經過 VisualVM 應用程序安裝,或者從 VisualVM 插件中心手動下載插件,而後離線安裝。另外,用戶還能夠經過下載插件分發文件 (.nbm 文件 ) 安裝第三方插件爲 VisualVM 添加功能。
從 VisualVM 插件中心安裝插件安裝步驟 :
一、從主菜單中選擇「工具」>「插件」。
二、在「可用插件」標籤中,選中該插件的「安裝」複選框。單擊「安裝」。
三、逐步完成插件安裝程序
根據 .nbm 文件安裝第三方插件安裝步驟 :
一、從主菜單中選擇「工具」>「插件」。
二、在「已下載」標籤中,點擊」添加插件」按鈕,選擇已下載的插件分發文件 (.nbm) 並打開。
三、選中打開的插件分發文件,並單擊」安裝」按鈕,逐步完成插件安裝程序
4、功能介紹
下面咱們將介紹性能分析的幾種常見方式以及如何使用 VisualVM 性能分析工具進行分析。
一、內存分析
VisualVM 經過檢測 JVM 中加載的類和對象信息等幫助咱們分析內存使用狀況,咱們能夠經過 VisualVM 的監視標籤和 Profiler 標籤對應用程序進行內存分析。
在監視標籤內,咱們能夠看到實時的應用程序內存堆以及永久保留區域的使用狀況。
內存堆使用狀況
永久保留區域使用狀況
此外,咱們也能夠經過 Applications 窗口右擊應用程序節點來啓用「在出現 OOME 時生成堆 Dump」功能,當應用程序出現 OutOfMemory 例外時,VisualVM 將自動生成一個堆轉儲。
開啓「在出現 OOME 時生成堆」功能
在 Profiler 標籤,點擊「內存」按鈕將啓動一個內存分析會話,等 VisualVM 收集和統計完相關性能數據信息,將會顯示在性能分析結果。經過內存性能分析結果,咱們能夠查看哪些對象佔用了較多的內存,存活的時間比較長等,以便作進一步的優化。
此外,咱們能夠經過性能分析結果下方的類名過濾器對分析結果進行過濾。
內存分析結果
二、CPU 分析
VisualVM 可以監控應用程序在一段時間的 CPU 的使用狀況,顯示 CPU 的使用率、方法的執行效率和頻率等相關數據幫助咱們發現應用程序的性能瓶頸。咱們能夠經過 VisualVM 的監視標籤和 Profiler 標籤對應用程序進行 CPU 性能分析。
在監視標籤內,咱們能夠查看 CPU 的使用率以及垃圾回收活動對性能的影響。太高的 CPU 使用率多是因爲咱們的項目中存在低效的代碼,能夠經過 Profiler 標籤的 CPU 性能分析功能進行詳細的分析。若是垃圾回收活動過於頻繁,佔用了較高的 CPU 資源,多是由內存不足或者是新生代和舊生代分配不合理致使的等。
CPU 使用狀況
在 Profiler 標籤,點擊「CPU」按鈕啓動一個 CPU 性能分析會話 ,VisualVM 會檢測應用程序全部的被調用的方法。當進入一個方法時,線程會發出一個「method entry」的事件,當退出方法時一樣會發出一個「method exit」的事件,這些事件都包含了時間戳。而後 VisualVM 會把每一個被調用方法的總的執行時間和調用的次數按照運行時長展現出來。
此外,咱們也能夠經過性能分析結果下方的方法名過濾器對分析結果進行過濾。
CPU 性能分析結果
三、線程分析
Java 語言可以很好的實現多線程應用程序。當咱們對一個多線程應用程序進行調試或者開發後期作性能調優的時候,每每須要瞭解當前程序中全部線程的運行狀態,是否有死鎖、熱鎖等狀況的發生,從而分析系統可能存在的問題。
在 VisualVM 的監視標籤內,咱們能夠查看當前應用程序中全部活動線程和守護線程的數量等實時信息。
活躍線程狀況
VisualVM 的線程標籤提供了三種視圖,默認會以時間線的方式展示。另外兩種視圖分別是表視圖和詳細信息視圖。
時間線視圖上方的工具欄提供了縮小,放大和自適應三個按鈕,以及一個下拉框,咱們能夠選擇將全部線程、活動線程或者完成的線程顯示在視圖中。
線程時間線視圖
線程表視圖
咱們在詳細信息視圖中不但能夠查看全部線程、活動線程和結束的線程的詳細數據,並且也能夠查看某個線程的詳細狀況。
線程詳細視圖
5、快照功能
咱們可使用 VisualVM 的快照功能生成任意個性能分析快照並保存到本地來輔助咱們進行性能分析。快照爲捕獲應用程序性能分析數據提供了一個很便捷的方式由於快照一旦生成能夠在任什麼時候候離線打開和查看,也能夠相互傳閱。
VisualVM 提供了兩種類型的快照:
一、Profiler 快照:當有一個性能分析會話(內存或者 CPU)正在進行時,咱們能夠經過性能分析結果工具欄的「快照」按鈕生成 Profiler 快照捕獲當時的性能分析數據。
Profiler 快照
二、應用程序快照:咱們能夠右鍵點擊左側 Applications 窗口中應用程序節點,選擇「應用程序快照」爲生成一個應用程序快照。應用程序快照會收集某一時刻的堆轉儲,線程轉儲和 Profiler 快照,同時也會捕獲 JVM 的一些基本信息。
應用程序快照
6、轉儲功能
一、線程轉儲的生成與分析
VisualVM 可以對正在運行的本地應用程序生成線程轉儲,把活動線程的堆棧蹤影打印出來,幫助咱們有效瞭解線程運行的狀況,診斷死鎖、應用程序癱瘓等問題。
線程標籤及線程轉儲功能
當 VisualVM 統計完應用程序內線程的相關數據,會把這些信息顯示新的線程轉儲標籤。
線程轉儲結果
二、堆轉儲的生成與分析
VisualVM 可以生成堆轉儲,統計某一特定時刻 JVM 中的對象信息,幫助咱們分析對象的引用關係、是否有內存泄漏狀況的發生等。
監視標籤及堆轉儲功能
當 VisualVM 統計完堆內對象數據後,會把堆轉儲信息顯示在新的堆轉儲標籤內,咱們能夠看到摘要、類、實例數等信息以及經過 OQL 控制檯執行查詢語句功能。
堆轉儲的摘要包括轉儲的文件大小、路徑等基本信息,運行的系統環境信息,也能夠顯示全部的線程信息。
堆轉儲的摘要視圖
從類視圖能夠得到各個類的實例數和佔用堆大小數,分析出內存空間的使用狀況,找出內存的瓶頸,避免內存的過分使用。
堆轉儲的類視圖
經過實例數視圖能夠得到每一個實例內部各成員變量的值以及該實例被引用的位置。首先須要在類視圖選擇須要查看實例的類。
選擇查詢實例數的類
實例數視圖
此外,還能對兩個堆轉儲文件進行比較。經過比較咱們可以分析出兩個時間點哪些對象被大量建立或銷燬。
堆轉儲的比較
堆轉儲的比較結果
線程轉儲和堆轉儲都可以另存成文件,以便進行離線分析。
轉儲文件的導出
7、實戰分析
一、簡要說明
打開jdk自帶的jvisualvm(bin目錄下),程序運行後會自動監控本機運行的java程序(Local標籤下,遠程服務器上的java程序須要另行配置),Local標籤下的第一個VisualVM爲jvisualvm對自身的監控,能夠看到消耗的資源仍是不多的,第二個爲本機的eclipse。
監控項總共分爲Overview,Monitor,Threads和一個Sampler。
(1)Overview(jvm啓動參數,系統參數)
能夠看到eclipse的啓動參數
(經過這些啓動參數,能夠判斷程序是否有內存溢出)
(2)Monitor
左上:cpu利用率,gc狀態的監控
右上:堆利用率,永久內存區的利用率
左下:類的監控
右下:線程的監控
performGC:gc的詳細運行狀態
HeapDump:堆的詳細狀態(能夠看到堆的概況,裏面全部的類,還能點進具體的一個類查看這個類的狀態)
(3)Threads
可以顯示線程的名稱和運行的狀態,在調試多線程時必不可少,並且能夠點進一個線程查看這個線程的詳細運行狀況
二、監控服務器上的tomcat
tomcat的配置文件catalina.sh中增長:
JAVA_OPTS="-Dcom.sun.management.jmxremote.port=9998
-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=192.168.58.164" 參數說明: 指定了JMX啓動的代理端口,這個端口就是visualvm要鏈接的端口(9998端口不能被別的程序使用netstat -an|gerp 9998) Dcom.sun.management.jmxremote.port=9998 指定了JMX是否啓用ssl Dcom.sun.management.jmxremote.authenticate=false 指定了JMX是否啓用鑑權(須要用戶名,密碼鑑權) Dcom.sun.management.jmxremote.authenticate=false 指定了服務器主機名 Djava.rmi.server.hostname=192.168.58.164
填寫主機名:
右鍵建立一個jmx鏈接,填寫上ip:port便可:
三、監控服務器上的java程序
相較於監控tomcat要麻煩不少,要預先啓動jstatd服務(${java_home}/bin目錄下)
jstatd是一個監控JVM從建立到銷燬過程當中資源佔用狀況並提供遠程監控接口的RMI(Remote Method Invocation,遠程方法調用)服務器程序,它是一個Daemon程序(後臺進程),要保證遠程監控軟件鏈接到本地的話須要jstatd始終保持運行。
jstatd運行須要經過-J-Djava.security.policy=*指定安全策略,所以咱們須要在服務器上創建一個指定安全策略的文件jstatd.all.policy(我放在了${java_home}/bin目錄下),文件內容以下:
grant codebase "file:/home/123/123/jdk1.5.0_15/lib/tools.jar" { permission java.security.AllPermission; };
而後使用這個策略文件啓動jstatd服務
[sys@sys bin]$ pwd
/home/sys/jdk1.5.0_15/bin [sys@123 sys]$ ./jstatd -J-Djava.security.policy=./jstatd.all.policy &
由於監控的過程當中須要jstatd服務一直運行,因此加上了&,若是須要日誌也可以使用:
./jstatd -J-Djava.security.policy=./jstatd.all.policy -J-Djava.rmi.server.logCalls=true
接下來就能夠在jvisualvm中配置監控該服務器上運行的java程序了,和在jvisualvm中配置監控tomcat服務器的操做過程是同樣的。須要特別注意的是,有時在配置遠程監控java程序的時候jvisualvm會報一個錯誤
點擊查看錯誤詳情:
connection refused to host:127.0.0.1初步判斷和主機名有關係。
[sys@sys bin]# hostname -i 127.0.0.1 [sys@sys bin]# hostname 192.168.58.168
修改完重啓jstatd服務(網上不少人說要修改主機的/etc/hosts文件,可是我本身測試修改/etc/hosts文件是沒有效果的,必需要修改主機名),Add Rempte Host,填寫主機名,以後這裏要選擇添加一個jstatd鏈接:
直接選擇默認配置便可(默認使用1099端口):
點擊ok後,168上的全部java程序就會自動列出:
注意:推薦一個很是好用的插件VisualGC,tool -> plugin ->aviable plugin:
安裝完這個插件後,將會增長新的監控條目Visual GC,能夠看到虛擬機內存各個區的使用狀況:
四、模擬內存泄漏
import java.util.HashMap;
import java.util.Map;
public class MemoryLeckTest { //聲明緩存對象 private static final Map map = new HashMap(); public static void main(String args[]){ try { Thread.sleep(10000);//給打開visualvm時間 } catch (InterruptedException e) { e.printStackTrace(); } //循環添加對象到緩存 for(int i=0; i<1000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("first"); //爲dump出堆提供時間 try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<1000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("second"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<3000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("third"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } for(int i=0; i<4000000;i++){ TestMemory t = new TestMemory(); map.put("key"+i,t); } System.out.println("forth"); try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("qqqq"); } }
配置的JVM參數以下:
-Xms512m -Xmx512m -XX:-UseGCOverheadLimit -XX:MaxPermSize=50m
使用jVisualvm分析內存泄漏:
查看Visual GC標籤,內容以下,這是輸出first的截圖
這是輸出forth的截圖:
經過2張圖對比發現:
老生代一直在gc,當程序繼續運行能夠發現老生代gc還在繼續:
增長到了7次,可是老生代的內存並無減小。說明存在沒法被回收的對象,多是內存泄漏了。
如何分析是那個對象泄漏了呢?打開抽樣器標籤:點擊後以下圖:
按照程序輸出進行堆dump,當輸出second時,dump一次,當輸出forth時dump一次。
進入最後dump出來的堆標籤,點擊類:
點擊右上角:「與另外一個堆存儲對比」。如圖選擇第一次導出的dump內容比較:
比較結果以下:
能夠看出在兩次間隔時間內TestMemory對象實例一直在增長而且多了,說明該對象引用的方法可能存在內存泄漏。
如何查看對象引用關係呢?
右鍵選擇類TestMemory,選擇「在實例視圖中顯示」,以下所示:
左側是建立的實例總數,右側上部爲該實例的結構,下面爲引用說明,從圖中能夠看出在類CyclicDependencies裏面被引用了,而且被HashMap引用。如此能夠肯定泄漏的位置,進而根據實際狀況進行分析解決。