文章目录
- 1. 介绍
- 2. 查看堆的变化
- 3. 查看堆快照
- 4. 导出堆快照文件
- 5. 查看class对象加载信息
- 6. CPU分析:发现cpu使用率最高的方法
- 7. 查看线程快照:发现死锁问题
1. 介绍
-
VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。
-
用于监控、故障诊断以及性能分析Java应用程序。JVisualVM 通过集成多个命令行JDK工具(如 jconsole, jinfo, jmap, jstack 等)的功能于一体,为用户提供了一个统一且易用的图形界面
位置:jdk\bin
作用:
- 查看应用jvm配置信息
- 查看cpu、内存、类、线程监控信息
- 查看堆的变化
- 查看堆快照
- 导出堆快照文件
- 查看class对象加载信息
- CPU分析:发现cpu使用率最高的方法
- 分析死锁问题,找到死锁的代码
界面如下:
2. 查看堆的变化
每隔3秒,堆内存使用新增100M
package com.xin.demo.threaddemo.bookdemo;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class JVisualVMDemo1 {
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);
}
}
}
查看内存变化
3. 查看堆快照
点击“监视”->”堆(dump)”可以生产堆快照信息.
4. 导出堆快照文件
点击右键另存为
可以用jvisualvm->文件->装入打开hprof文件,打开后如下图:
5. 查看class对象加载信息
package com.xin.demo.threaddemo.bookdemo;
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 JVisualVMDemo2 {
private static List<Object> insList = new ArrayList<>();
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("D:\\javasoft\\apache-maven-3.2.1\\mavenrepository\\commons-logging\\commons-logging\\1.2");
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;
}
}
下图,可以观察到元空间、类的变化成正相关
6. CPU分析:发现cpu使用率最高的方法
CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。
没有程序运行时的 CPU 使用情况如下图:
下面我们写一个cpu占用率比较高的程序。
package com.xin.demo.threaddemo.bookdemo;
public class JVisualVMDemo3 {
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);
}
}
}
过高的 CPU 使用率可能是我们的程序代码性能有问题导致的。可以切换到“抽样器”对cpu进行采样,可以擦看到那个方法占用的cpu最高,然后进行优化。
从图中可以看出cpuFix方法使用cpu最多,然后就可以进行响应的优化了。
7. 查看线程快照:发现死锁问题
Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生,从而分析系统可能存在的问题。
在 VisualVM 的监视标签内,我们可以查看当前应用程序中所有活动线程(Live threads)和守护线程(Daemon threads)的数量等实时信息。
可以查看线程快照,发现系统的死锁问题。
下面我们将通过visualvm来排查一个死锁问题。
package com.xin.demo.threaddemo.bookdemo;
public class JVisualVMDemo4 {
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;
}
@Override
public 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,生成线程堆栈信息:
上面这段信息可以看出,thread1持有Obj1对象的锁,等待获取Obj2的锁,thread2持有Obj2的锁,等待获取Obj1的锁,导致了死锁。