轉(zhuǎn)自:http://www.cnblogs.com/wade-xu/p/4369094.html
這篇總結(jié)的很不錯(本人親自操手學(xué)習(xí))鲫懒,留著以后復(fù)習(xí)備用诡壁,很適合入門級的學(xué)習(xí)者:
VisualVM 是一款免費的逃沿,集成了多個 JDK 命令行工具的可視化工具,它能為您提供強大的分析能力唉韭,對 Java 應(yīng)用程序做性能分析和調(diào)優(yōu)柔逼。這些功能包括生成和分析海量數(shù)據(jù)、跟蹤內(nèi)存泄漏享钞、監(jiān)控垃圾回收器揍诽、執(zhí)行內(nèi)存和 CPU 分析,同時它還支持在 MBeans 上進行瀏覽和操作栗竖。本文主要介紹如何使用 VisualVM 進行性能分析及調(diào)優(yōu)暑脆。
準(zhǔn)備工作
自從 JDK 6 Update 7 以后已經(jīng)作為 Oracle JDK 的一部分,位于 JDK 根目錄的 bin 文件夾下狐肢,無需安裝添吗,直接運行即可。
?內(nèi)存分析篇
VisualVM 通過檢測 JVM 中加載的類和對象信息等幫助我們分析內(nèi)存使用情況份名,我們可以通過 VisualVM 的監(jiān)視標(biāo)簽對應(yīng)用程序進行內(nèi)存分析碟联。
1)內(nèi)存堆Heap
首先我們來看內(nèi)存堆Heap使用情況,我本機eclipse的進程在visualVM顯示如下:
隨便寫個小程序占用內(nèi)存大的僵腺,運行一下
程序如下:
1package jvisualVM; 2 3publicclass JavaHeapTest { 4publicfinalstaticintOUTOFMEMORY = 200000000; 5 6private String oom; 7 8privateint length; 910StringBuffer tempOOM =new StringBuffer();1112publicJavaHeapTest(int leng) {13this.length = leng;1415inti = 0;16while(i < leng) {17i++;18try {19tempOOM.append("a");20}catch (OutOfMemoryError e) {21? ? ? ? ? ? ? e.printStackTrace();22break;23? ? ? ? ? ? }24? ? ? ? }25this.oom = tempOOM.toString();2627? ? }2829public String getOom() {30return oom;31? ? }3233publicint getLength() {34return length;35? ? }3637publicstaticvoid main(String[] args) {38JavaHeapTest javaHeapTest =new JavaHeapTest(OUTOFMEMORY);39? ? ? ? System.out.println(javaHeapTest.getOom().length());40? ? }4142}
查看VisualVM Monitor tab, 堆內(nèi)存變大了
在程序運行結(jié)束之前鲤孵, 點擊Heap Dump 按鈕, 等待一會兒辰如,得到dump結(jié)果普监,可以看到一些Summary信息
點擊Classes,?發(fā)現(xiàn)char[]所占用的內(nèi)存是最大的
雙擊它琉兜,得到如下Instances結(jié)果
Instances是按Size由大到小排列的
第一個就是最大的凯正, 展開Field區(qū)域的 values
StringBuffer類型的 全局變量 tempOOM 占用內(nèi)存特別大, 注意局部變量是無法通過 堆dump來得到分析結(jié)果的呕童。
另外漆际,對于“堆 dump”來說淆珊,在遠程監(jiān)控jvm的時候夺饲,VisualVM是沒有這個功能的,只有本地監(jiān)控的時候才有。
###轉(zhuǎn)載注明出處:http://www.cnblogs.com/wade-xu/p/4369094.html
2)永久保留區(qū)域PermGen
其次來看下永久保留區(qū)域PermGen使用情況
運行一段類加載的程序往声,代碼如下:
1package jvisualVM; 2 3import java.io.File; 4import java.lang.reflect.Method; 5import java.net.MalformedURLException; 6import java.net.URL; 7import java.net.URLClassLoader; 8import java.util.ArrayList; 9import java.util.List;1011publicclass TestPermGen {1213privatestaticList insList =newArrayList();1415publicstaticvoidmain(String[] args)throws Exception {1617? ? ? ? permLeak();18? ? }1920privatestaticvoidpermLeak()throws Exception {21for(inti = 0; i < 1000; i++) {22URL[] urls = getURLS();23URLClassLoader urlClassloader =newURLClassLoader(urls,null);24Class logfClass = Class.forName("org.apache.commons.logging.LogFactory",true,urlClassloader);25Method getLog = logfClass.getMethod("getLog", String.class);26Object result = getLog.invoke(logfClass, "TestPermGen");27? ? ? ? ? ? insList.add(result);28System.out.println(i + ": " + result);29? ? ? ? }30? ? }3132privatestaticURL[] getURLS()throws MalformedURLException {33File libDir =newFile("C:/Users/wadexu/.m2/repository/commons-logging/commons-logging/1.1.1");34File[] subFiles = libDir.listFiles();35intcount = subFiles.length;36URL[] urls =new URL[count];37for(inti = 0; i < count; i++) {38urls[i] = subFiles[i].toURI().toURL();39? ? ? ? }40return urls;41? ? }424344}
一個類型裝載之后會創(chuàng)建一個對應(yīng)的java.lang.Class實例擂找,這個實例本身和普通對象實例一樣存儲于堆中,我覺得之所以說是這是一種特殊的實例浩销,某種程度上是因為其充當(dāng)了訪問PermGen區(qū)域中類型信息的代理者贯涎。
?運行一段時間后拋OutOfMemoryError了, VisualVM監(jiān)控結(jié)果如下:
結(jié)論:PermGen區(qū)域分配的堆空間過小慢洋,我們可以通過設(shè)置-XX: PermSize參數(shù)和-XX:MaxPermSize參數(shù)來解決塘雳。
關(guān)于PermGen OOM深入分析請參考這篇文章
關(guān)于Perform GC, 請參考這篇文章
###轉(zhuǎn)載注明出處:http://www.cnblogs.com/wade-xu/p/4369094.html
CPU分析篇
CPU 性能分析的主要目的是統(tǒng)計函數(shù)的調(diào)用情況及執(zhí)行時間,或者更簡單的情況就是統(tǒng)計應(yīng)用程序的 CPU 使用情況普筹。
沒有程序運行時的 CPU 使用情況如下圖:
運行一段 占用CPU 的小程序败明,代碼如下
package jvisualVM;publicclass MemoryCpuTest {
? ? publicstaticvoidmain(String[] args)throws InterruptedException {
? ? ? ? cpuFix();
? ? }
? ? /**? ? * cpu 運行固定百分比
? ? *
? ? * @throws InterruptedException
? ? */publicstaticvoidcpuFix()throws InterruptedException {
? ? ? ? // 80%的占有率intbusyTime = 8;
? ? ? ? // 20%的占有率intidelTime = 2;
? ? ? ? // 開始時間longstartTime = 0;
? ? ? ? while(true) {
? ? ? ? ? ? // 開始時間startTime = System.currentTimeMillis();
? ? ? ? ? ? /*? ? ? ? ? ? * 運行時間
? ? ? ? ? ? */while(System.currentTimeMillis() - startTime < busyTime) {
? ? ? ? ? ? ? ? ;
? ? ? ? ? ? }
? ? ? ? ? ? // 休息時間? ? ? ? ? ? Thread.sleep(idelTime);
? ? ? ? }
? ? }
}
查看監(jiān)視頁面 Monitor tab
過高的 CPU 使用率可能是由于我們的項目中存在低效的代碼;
在我們對程序施壓的時候太防,過低的 CPU 使用率也有可能是程序的問題妻顶。
點擊取樣器Sampler, 點擊“CPU”按鈕蜒车, 啟動CPU性能分析會話讳嘱,VisualVM 會檢測應(yīng)用程序所有的被調(diào)用的方法,
在CPU samples tab 下可以看到我們的方法cpufix() 的自用時間最長酿愧, 如下圖:
切換到Thread CPU Time 頁面下沥潭,我們的 main 函數(shù)這個進程 占用CPU時間最長, 如下圖:
###轉(zhuǎn)載注明出處:http://www.cnblogs.com/wade-xu/p/4369094.html
線程分析篇
Java 語言能夠很好的實現(xiàn)多線程應(yīng)用程序嬉挡。當(dāng)我們對一個多線程應(yīng)用程序進行調(diào)試或者開發(fā)后期做性能調(diào)優(yōu)的時候叛氨,往往需要了解當(dāng)前程序中所有線程的運行狀態(tài),是否有死鎖棘伴、熱鎖等情況的發(fā)生寞埠,從而分析系統(tǒng)可能存在的問題。
在 VisualVM 的監(jiān)視標(biāo)簽內(nèi)焊夸,我們可以查看當(dāng)前應(yīng)用程序中所有活動線程(Live threads)和守護線程(Daemon threads)的數(shù)量等實時信息仁连。
運行一段小程序,代碼如下:
package jvisualVM;publicclassMyThreadextends Thread{
? ? publicstaticvoid main(String[] args) {
? ? ? ? MyThread mt1 =newMyThread("Thread a");
? ? ? ? MyThread mt2 =newMyThread("Thread b");
? ? ? ? mt1.setName("My-Thread-1 ");
? ? ? ? mt2.setName("My-Thread-2 ");
? ? ? ? mt1.start();
? ? ? ? mt2.start();
? ? }
? ? public MyThread(String name) {
? ? }
? ? publicvoid run() {
? ? ? ? while(true) {
? ? ? ? }
? ? }
}
Live threads 從11增加兩個 變成13了
Daemon threads從8增加兩個 變成10了?
VisualVM 的線程標(biāo)簽提供了三種視圖阱穗,默認會以時間線的方式展現(xiàn)饭冬, 如下圖:
可以看到兩個我們run的程序里啟的線程:My-Thread-1 和?My-Thread-2
另外還有兩種視圖分別是表視圖和詳細信息視圖, 這里看一下每個Thread的詳細視圖:
###轉(zhuǎn)載注明出處:http://www.cnblogs.com/wade-xu/p/4369094.html
?再來一段死鎖的程序揪阶,看VisualVM 能否分析出來
package jvisualVM;publicclass DeadLock {
? ? publicstaticvoid main(String[] args) {
? ? ? ? Resource r1 =new Resource();
? ? ? ? Resource r0 =new Resource();
? ? ? ? Thread myTh1 =new LockThread1(r1, r0);
? ? ? ? Thread myTh0 =new LockThread0(r1, r0);
? ? ? ? myTh1.setName("DeadLock-1 ");
? ? ? ? myTh0.setName("DeadLock-0 ");
? ? ? ? myTh1.start();
? ? ? ? myTh0.start();
? ? }
}
? ? class Resource {
? ? ? ? privateint i;
? ? ? ? publicint getI() {
? ? ? ? ? ? return i;
? ? ? ? }
? ? ? ? publicvoidsetI(int i) {
? ? ? ? ? ? this.i = i;
? ? ? ? }
? ? }
? ? classLockThread1extends Thread {
? ? ? ? private Resource r1, r2;
? ? ? ? public LockThread1(Resource r1, Resource r2) {
? ? ? ? ? ? this.r1 = r1;
? ? ? ? ? ? this.r2 = r2;
? ? ? ? }
? ? ? ? @Override
? ? ? ? publicvoid run() {
? ? ? ? ? ? intj = 0;
? ? ? ? ? ? while(true) {
? ? ? ? ? ? ? ? synchronized (r1) {
? ? ? ? ? ? ? ? ? ? System.out.println("The first thread got r1's lock " + j);
? ? ? ? ? ? ? ? ? ? synchronized (r2) {
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("The first thread got r2's lock? " + j);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? j++;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? classLockThread0extends Thread {
? ? ? ? private Resource r1, r2;
? ? ? ? public LockThread0(Resource r1, Resource r2) {
? ? ? ? ? ? this.r1 = r1;
? ? ? ? ? ? this.r2 = r2;
? ? ? ? }
? ? ? ? @Override
? ? ? ? publicvoid run() {
? ? ? ? ? ? intj = 0;
? ? ? ? ? ? while(true) {
? ? ? ? ? ? ? ? synchronized (r2) {
? ? ? ? ? ? ? ? ? ? System.out.println("The second thread got r2's lock? " + j);
? ? ? ? ? ? ? ? ? ? synchronized (r1) {
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("The second thread got r1's lock" + j);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? j++;
? ? ? ? ? ? }
? ? ? ? }
? ? }
打開VisualVM檢測到的JVM進程昌抠,我們可以看到這個tab在閃,VisualVM已經(jīng)檢測到我這個package下面的DeadLock類出錯了
切換到Thread tab鲁僚, 可以看到死鎖了炊苫,?Deadlock detected!
另外可以點擊Thread Dump 線程轉(zhuǎn)儲裁厅,進一步分析,在這里就不贅述了侨艾,有興趣的讀者可以自行實驗执虹。
參考文獻:
http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/