jvisualvm多合一故障處理工具
1、visualvm介紹
VisualVM
是一款免費的,集成了多個 JDK 命令行工具的可視化工具,它能為您提供強大的分析能力,對 Java 應
用程序做性能分析和調優。這些功能包括生成和分析海量數據、跟蹤內存泄漏、監控垃圾回收器、執行內存和
CPU 分析,同時它還支持在 MBeans 上進行瀏覽和操作。
本文主要介紹如何使用 VisualVM 進行性能分析及調優。
VisualVM位于{JAVA_HOME}/bin
目錄中。
在linux和windows下通過jvisualvm
啟動。
下面我們來看visualvm的各種功能。
2、查看jvm配置信息
第一步:點擊左邊窗口顯示正在運行的java進程
第二步:點擊右側窗口概述
,可以查看各種配置信息
通過jdk提供的jinfo
命令工具也可以查看上面的信息。
3、查看cpu、內存、類、線程監控信息
4、查看堆的變化
步驟一:運行下面的代碼
每隔3秒,堆內存使用新增100M。
package com.example.controller;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;public class Demo4 {public static final int _1M = 1024 * 1024;public static void main(String[] args) throws InterruptedException {List<Object> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {list.add(new byte[100 * _1M]);TimeUnit.SECONDS.sleep(3);System.out.println(i);}}
}
步驟二:在VisualVM可以很清晰的看到堆內存變化信息。
4、查看堆快照
步驟一:點擊監視->堆(dump)
可以生產堆快照信息.
生成了以heapdump開頭的一個選項卡,內容如下:
對于堆 dump
來說,在遠程監控jvm的時候,VisualVM是沒有這個功能的,只有本地監控的時候才有。
5、導出堆快照文件
步驟一:查看堆快照,此步驟可以參考上面的查看堆快照
功能
步驟二:右鍵點擊另存為,即可導出hprof堆快照文件,可以發給其他同事分析使用
6、查看class對象加載信息
這次來看下永久保留區域PermGen使用情況。
步驟一:運行一段類加載的程序。
package com.example.controller;import java.io.File;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;public class Demo5 {private static List<Object> insList = new ArrayList<Object>();public static void main(String[] args) throws Exception {permLeak();}private static void permLeak() throws Exception {for (int i = 0; i < 2000; i++) {URL[] urls = getURLS();URLClassLoader urlClassloader = new URLClassLoader(urls, null);Class<?> logfClass = Class.forName("org.apache.commons.logging.LogFactory", true, urlClassloader);Method getLog = logfClass.getMethod("getLog", String.class);Object result = getLog.invoke(logfClass, "TestPermGen");insList.add(result);System.out.println(i + ": " + result);if (i % 100 == 0) {TimeUnit.SECONDS.sleep(1);}}}private static URL[] getURLS() throws MalformedURLException {File libDir = new File("C:\\zsxsoftware\\apache-maven-3.3.9-bin\\repository\\commons-logging\\commons-logging\\1.1.1");File[] subFiles = libDir.listFiles();int count = subFiles.length;URL[] urls = new URL[count];for (int i = 0; i < count; i++) {urls[i] = subFiles[i].toURI().toURL();}return urls;}
}
步驟二:打開visualvm查看,metaspace。
7、CPU分析:發現cpu使用率最高的方法
CPU 性能分析的主要目的是統計函數的調用情況及執行時間,或者更簡單的情況就是統計應用程序的 CPU 使用情
況。沒有程序運行時的 CPU 使用情況如下圖:
下面我們寫一個cpu占用率比較高的程序。
步驟一:運行下列程序。
package com.example.controller;public class Demo6 {public static void main(String[] args) throws InterruptedException {cpuFix();}/*** cpu 運行固定百分比** @throws InterruptedException*/public static void cpuFix() throws InterruptedException {// 80%的占有率int busyTime = 8;// 20%的占有率int idelTime = 2;// 開始時間long startTime = 0;while (true) {// 開始時間startTime = System.currentTimeMillis();/** 運行時間*/while (System.currentTimeMillis() - startTime < busyTime) {;}// 休息時間Thread.sleep(idelTime);}}
}
步驟二:打開visualvm查看cpu使用情況,我的電腦是8核的,如下圖:
過高的 CPU 使用率可能是我們的程序代碼性能有問題導致的。可以切換到抽樣器
對cpu進行采樣,可以擦看到那
個方法占用的cpu最高,然后進行優化。
從圖中可以看出cpuFix方法使用cpu最多,然后就可以進行響應的優化了。
8、查看線程快照:發現死鎖問題
Java 語言能夠很好的實現多線程應用程序。當我們對一個多線程應用程序進行調試或者開發后期做性能調優的時
候,往往需要了解當前程序中所有線程的運行狀態,是否有死鎖、熱鎖等情況的發生,從而分析系統可能存在的問
題。
在 VisualVM 的監視標簽內,我們可以查看當前應用程序中所有活動線程(Live threads)和守護線程(Daemon
threads)的數量等實時信息。
可以查看線程快照,發現系統的死鎖問題。
下面我們將通過visualvm來排查一個死鎖問題。
步驟一:運行下面的代碼:
package com.example.controller;public class Demo7 {public static void main(String[] args) {Obj1 obj1 = new Obj1();Obj2 obj2 = new Obj2();Thread thread1 = new Thread(new SynAddRunalbe(obj1, obj2, 1, 2, true));thread1.setName("thread1");thread1.start();Thread thread2 = new Thread(new SynAddRunalbe(obj1, obj2, 2, 1, false));thread2.setName("thread2");thread2.start();}/*** 線程死鎖等待演示*/public static class SynAddRunalbe implements Runnable {Obj1 obj1;Obj2 obj2;int a, b;boolean flag;public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) {this.obj1 = obj1;this.obj2 = obj2;this.a = a;this.b = b;this.flag = flag;}@Overridepublic void run() {try {if (flag) {synchronized (obj1) {Thread.sleep(100);synchronized (obj2) {System.out.println(a + b);}}} else {synchronized (obj2) {Thread.sleep(100);synchronized (obj1) {System.out.println(a + b);}}}} catch (InterruptedException e) {e.printStackTrace();}}}public static class Obj1 {}public static class Obj2 {}
}
程序中:thread1持有obj1的鎖,thread2持有obj2的鎖,thread1等待獲取obj2的鎖,thread2等待獲取obj1的
鎖,相互需要獲取的鎖都被對方持有者,造成了死鎖。程序中出現了死鎖的情況,我們是比較難以發現的。需要依
靠工具解決。
步驟二:打開visualvm查看堆棧信息:
點擊dump,生成線程堆棧信息:
可以看到Found one Java-level deadlock,包含了導致死鎖的代碼。
Found one Java-level deadlock:
=============================
"thread2":waiting to lock monitor 0x000000001d201fd8 (object 0x000000076b5ff7c8, a com.example.controller.Demo7$Obj1),which is held by "thread1"
"thread1":waiting to lock monitor 0x000000001d203478 (object 0x000000076b601e50, a com.example.controller.Demo7$Obj2),which is held by "thread2"Java stack information for the threads listed above:
===================================================
"thread2":at com.example.controller.Demo7$SynAddRunalbe.run(Demo7.java:47)- waiting to lock <0x000000076b5ff7c8> (a com.example.controller.Demo7$Obj1)- locked <0x000000076b601e50> (a com.example.controller.Demo7$Obj2)at java.lang.Thread.run(Thread.java:745)
"thread1":at com.example.controller.Demo7$SynAddRunalbe.run(Demo7.java:40)- waiting to lock <0x000000076b601e50> (a com.example.controller.Demo7$Obj2)- locked <0x000000076b5ff7c8> (a com.example.controller.Demo7$Obj1)at java.lang.Thread.run(Thread.java:745)Found 1 deadlock.
上面這段信息可以看出,thread1持有Obj1對象的鎖,等待獲取Obj2的鎖,thread2持有Obj2的鎖,等待獲取
Obj1的鎖,導致了死鎖。