jdk可视化工具系列——检视阅读

jdk可视化工具系列——检视阅读

参考

java虚拟机系列

RednaxelaFX知乎问答

RednaxelaFX博客

JConsole——Java监视与管理控制台

jconsole介绍

JConsole(java monitoring and management console)是一款基于JMX(Java Management Extensions,即Java管理扩展)的可视化监视和管理工具。

启动JConsole

  1. 点击JDK/bin 目录下面的“jconsole.exe”即可启动
  2. 然后会自动搜索本机运行的所有虚拟机进程
  3. 选择其中一个进程可开始进行监控

JConsole基本介绍

JConsole 基本包括以下基本功能:概述、内存、线程、类、VM概要、MBean

运行下面的程序、然后使用JConsole进行监控;注意设置虚拟机参数

/**
* <p>
* 设置虚拟机参数:-Xms100M -Xmx100m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/dump1.hprof
*/
public class Demo1 {
static class OOMObject {
//public byte[] placeholder = new byte[64 * 1024];
public byte[] placeholder = new byte[64 * 1024 * 1024]; //create OOM
} public static void fillHeap(int num) throws InterruptedException {
Thread.sleep(20000); //先运行程序,在执行监控
List<OOMObject> list = new ArrayList<OOMObject>();
for (int i = 0; i < num; i++) {
// 稍作延时,令监视曲线的变化更加明显
Thread.sleep(50);
list.add(new OOMObject());
}
System.gc();
} public static void main(String[] args) throws Exception {
fillHeap(1000);
while (true) {
//让其一直运行着
}
}
}

打开JConsole查看上面程序

可以切换顶部的选项卡查看各种指标信息。

内存监控——可视化的jstat 命令

“内存”页签相当于可视化的jstat 命令,用于监视受收集器管理的虚拟机内存的变换趋势。

还是上面的程序:

代码运行,控制台也会输出gc日志:(因为我们添加了参数:-XX:+PrintGCDetails)

、[GC (Allocation Failure) [DefNew: 27273K->3392K(30720K), 0.0120476 secs] 27273K->15632K(99008K), 0.0120955 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 30667K->3392K(30720K), 0.0152203 secs] 42907K->38103K(99008K), 0.0152500 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC (Allocation Failure) [DefNew: 30710K->3376K(30720K), 0.0297297 secs] 65422K->63609K(99008K), 0.0297929 secs] [Times: user=0.03 sys=0.00, real=0.03 secs]
[Full GC (System.gc()) [Tenured: 60232K->66947K(68288K), 0.0107243 secs] 67227K->66947K(99008K), [Metaspace: 9501K->9501K(1058816K)], 0.0107834 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [DefNew: 27328K->27328K(30720K), 0.0000201 secs][Tenured: 66947K->2319K(68288K), 0.0147126 secs] 94275K->2319K(99008K), [Metaspace: 9724K->9724K(1058816K)], 0.0148269 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] //placeholder加大测试生产dump文件
[GC (Allocation Failure) [DefNew: 4946K->1232K(30720K), 0.0019894 secs][Tenured: 65536K->66767K(68288K), 0.0027748 secs] 70482K->66767K(99008K), [Metaspace: 3477K->3477K(1056768K)], 0.0048224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [Tenured: 66767K->66746K(68288K), 0.0016820 secs] 66767K->66746K(99008K), [Metaspace: 3477K->3477K(1056768K)], 0.0017023 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to D:/dump1.hprof ...
Unable to create D:/dump1.hprof: File exists
Heap
def new generation total 30720K, used 820K [0x00000000f9c00000, 0x00000000fbd50000, 0x00000000fbd50000)
eden space 27328K, 3% used [0x00000000f9c00000, 0x00000000f9ccd0e0, 0x00000000fb6b0000)
from space 3392K, 0% used [0x00000000fba00000, 0x00000000fba00000, 0x00000000fbd50000)
to space 3392K, 0% used [0x00000000fb6b0000, 0x00000000fb6b0000, 0x00000000fba00000)
tenured generation total 68288K, used 66746K [0x00000000fbd50000, 0x0000000100000000, 0x0000000100000000)
the space 68288K, 97% used [0x00000000fbd50000, 0x00000000ffe7e980, 0x00000000ffe7ea00, 0x0000000100000000)
Metaspace used 3512K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 381K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.self.test.Demo1$OOMObject.<init>(Demo1.java:19)
at com.self.test.Demo1.fillHeap(Demo1.java:28)
at com.self.test.Demo1.main(Demo1.java:34)

线程监控——可视化的jstack命令

如果上面的“内存”页签相当于可视化的jstat命令的话,“线程”页签的功能相当于可视化的jstack命令,遇到线程停顿时可以使用这个页签进行监控分析。线程长时间停顿的主要原因主要有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(活锁和死锁)

下面三个方法分别等待控制台输入、死循环演示、线程锁等待演示

第一步:运行如下代码:

public class Demo2 {
public static void main(String[] args) throws IOException {
waitRerouceConnection();
createBusyThread();
createLockThread(new Object());
} /**
* 等待控制台输入
*
* @throws IOException
*/
public static void waitRerouceConnection() throws IOException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
}, "waitRerouceConnection");
thread.start(); } /**
* 线程死循环演示
*/
public static void createBusyThread() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
;
}
}
}, "testBusyThread");
thread.start();
} /**
* 线程锁等待演示
*/
public static void createLockThread(final Object lock) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "testLockThread");
thread.start();
}
}

第二步:打开jconsole中查看上面程序运行情况,可以查看到3个目标线程

第三步:查看目标线程信息

waitRerouceConnection线程处于读取数据状态,如上图:

testBusyThread线程位于代码45行,处于运行状态,如下图:

testLockThread处于活锁等待状态,如下图:

只要lock对象的notify()或notifyAll()方法被调用,这个线程便可能激活以继续执行

通过“线程”这个窗口可以很方便查询虚拟机中的线程堆栈信息,对发现系统中的一些问题非常有帮助。

线程死锁演示

第一步:运行下面代码:

package com.jvm.jconsole;

/**
* <b>description</b>: <br>
* <b>time</b>:2019/6/2 22:39 <br>
* <b>author</b>:ready likun_557@163.com
*/
public class Demo3 {
public static void main(String[] args) {
User u1 = new User("u1");
User u2 = new User("u2");
Thread thread1 = new Thread(new SynAddRunalbe(u1, u2, 1, 2, true));
thread1.setName("thread1");
thread1.start();
Thread thread2 = new Thread(new SynAddRunalbe(u1, u2, 2, 1, false));
thread2.setName("thread2");
thread2.start();
} /**
* 线程死锁等待演示
*/
public static class SynAddRunalbe implements Runnable {
User u1, u2;
int a, b;
boolean flag; public SynAddRunalbe(User u1, User u2, int a, int b, boolean flag) {
this.u1 = u1;
this.u2 = u2;
this.a = a;
this.b = b;
this.flag = flag;
} @Override
public void run() {
try {
if (flag) {
synchronized (u1) {
Thread.sleep(100);
synchronized (u2) {
System.out.println(a + b);
}
}
} else {
synchronized (u2) {
Thread.sleep(100);
synchronized (u1) {
System.out.println(a + b);
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static class User {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public User(String name) {
this.name = name;
} @Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
}

thread1持有u1的锁,thread2持有u2的锁,thread1等待获取u2的锁,thread2等待获取u1的锁,相互需要获取的锁都被对方持有者,造成了死锁。程序中出现了死锁的情况,我们是比较难以发现的。需要依靠工具解决。刚好jconsole就是这个美妙的工具。

第二步:在jconsole中打开上面程序的监控信息:

从上面可以看出代码48行和55行处导致了死锁。

关于程序死锁的,我们还可以使用命令行工具jstack来查看java线程堆栈信息,也可以发现死锁。

jvisualvm——多合一故障处理工具

VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。

VisualVM位于{JAVA_HOME}/bin目录中。

查看jvm配置信息

第一步:双击左边窗口显示正在运行的java进程

第二步:点击右侧窗口“概述”,可以查看各种配置信息

通过jdk提供的jinfo命令工具也可以查看上面的信息。

查看cpu、内存、类、线程监控信息——检视tab页

查看堆的变化

步骤一:运行下面的代码

每隔3秒,堆内存使用新增100M

public class Demo1 {
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可以很清晰的看到堆内存变化信息。

查看堆快照

步骤一:点击“监视”->"堆(dump)"可以生产堆快照信息.

生成了以heapdump开头的一个选项卡,内容如下:

对于“堆 dump”来说,在远程监控jvm的时候,VisualVM是没有这个功能的,只有本地监控的时候才有。

导出堆快照文件

步骤一:查看堆快照,此步骤可以参考上面的“查看堆快照”功能

步骤二:右键点击另存为,即可导出hprof堆快照文件,可以发给其他同事分析使用

查看class对象加载信息

其次来看下永久保留区域PermGen使用情况

步骤一:运行一段类加载的程序,代码如下:

public class Demo2 {

    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("D:\\installsoft\\maven\\.m2\\repository3.3.9_0\\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

CPU分析:发现cpu使用率最高的方法

CPU 性能分析的主要目的是统计函数的调用情况及执行时间,或者更简单的情况就是统计应用程序的 CPU 使用情况。

下面我们写一个cpu占用率比较高的程序。

步骤一:运行下列程序:

public class Demo3 {
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使用情况,我的电脑是4核的,如下图:

过高的 CPU 使用率可能是我们的程序代码性能有问题导致的。可以切换到“抽样器”对cpu进行采样,可以查看到哪个方法占用的cpu最高,然后进行优化。

从图中可以看出cpuFix方法使用cpu最多,然后就可以进行响应的优化了。

查看线程快照:发现死锁问题

Java 语言能够很好的实现多线程应用程序。当我们对一个多线程应用程序进行调试或者开发后期做性能调优的时候,往往需要了解当前程序中所有线程的运行状态,是否有死锁、热锁等情况的发生,从而分析系统可能存在的问题。

在 VisualVM 的监视标签内,我们可以查看当前应用程序中所有活动线程(Live threads)和守护线程(Daemon threads)的数量等实时信息。

可以查看线程快照,发现系统的死锁问题。

下面我们将通过visualvm来排查一个死锁问题。

步骤一:运行下面的代码:

public class Demo4 {

    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,生成线程堆栈信息,还可以右击threaddump生产线程dump分析。

可以看到“Found one Java-level deadlock”,包含了导致死锁的代码。

"thread2":
at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:50)
- waiting to lock <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
- locked <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
at java.lang.Thread.run(Thread.java:745)
"thread1":
at com.jvm.visualvm.Demo4$SynAddRunalbe.run(Demo4.java:43)
- waiting to lock <0x00000007173d6310> (a com.jvm.visualvm.Demo4$Obj2)
- locked <0x00000007173d40f0> (a com.jvm.visualvm.Demo4$Obj1)
at java.lang.Thread.run(Thread.java:745)

上面这段信息可以看出,thread1持有Obj1对象的锁,等待获取Obj2的锁,thread2持有Obj2的锁,等待获取Obj1的锁,导致了死锁。

总结

本文介绍了jdk提供的一款非常强大的分析问题的一个工具VisualVM,通过他,我们可以做一下事情:

  1. 查看应用jvm配置信息
  2. 查看cpu、内存、类、线程监控信息
  3. 查看堆的变化
  4. 查看堆快照
  5. 导出堆快照文件
  6. 查看class对象加载信息
  7. CPU分析:发现cpu使用率最高的方法
  8. 分析死锁问题,找到死锁的代码
上一篇:通过JCONSOLE监控TOMCAT的JVM使用情况


下一篇:JVM学习总结五(番外)——JConsole