监控jvm工具:visualVM
[TOC]
为什么要监控jvm
众所周知,jvm(java virtual mechine)是java的精髓。
秉持着“一次编码,到处运行”的设计理念,可以说jvm让java在90年代火到了21世纪至今
由c++开发的jvm,它巧妙地设计了java的设计理念——即万物皆对象。并设计了这些对象应该如何存储,如何调用,并通过不断迭代设计让对象的存储和回收,执行更加合理,下图是jvm的发展历程。
时至今日,jvm这种“虚拟机”设计来运行的语言和生态仍然丰富。比如Kotlin,Groovy,JRuby(也就是说java可能会老,但jvm不会)。
也许开发者未必精通jvm,但对jvm的深入了解可以对开发,排错,调优有非常大的帮助。这里给出最基本的 jvm 入门知识,也是你监控 jvm并期待通过分析jvm来排错调优所必要的基础知识:
堆(Heap):
- 堆是用于存储对象实例的内存区域。在堆中分配的对象包括通过
new
关键字创建的对象以及数组。 - 所有线程共享堆,但每个对象都有一个标识它的引用。
- 堆是用于存储对象实例的内存区域。在堆中分配的对象包括通过
栈(Stack):
- 栈用于存储方法的局部变量、操作数栈、方法返回地址等。每个线程都有自己的栈。
- 每个方法在执行时都会创建一个栈帧,栈帧包含了该方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。
程序计数器(Program Counter):
- 程序计数器是每个线程私有的,用于存储当前线程执行的字节码指令的地址或索引。
- 在多线程环境下,程序计数器用于记录每个线程执行的位置,确保线程切换后能够恢复到正确的执行位置。
本地方法栈(Native Method Stack):
- 本地方法栈类似于栈,但用于执行本地方法(非 Java 语言编写的方法)。
- 本地方法栈的实现和栈类似,但用于执行本地代码。
元空间(Metaspace):
- 元空间是 JDK 8+ 版本中引入的(替代原来的方法区),使用本机内存存储类的元信息,包括类的结构信息、静态变量、方法信息等。
堆内提供垃圾回收,通过监视和清理不再使用的对象,释放内存空间,避免内存泄漏和提高程序性能
VisualVM分析
事实上,由于jvm的不断发展,官方(无论是jdk还是jvm)提供了很多命令和接口编程去帮助开发者监控jvm。
而其中本文详细介绍的VisualVM则是一款官方的,简单可视化的jvm监控工具。
下载安装
官网:https://visualvm.github.io/download.html
Q:如何汉化?
A:请从%JAVA_HOME%/bin下的jvisualvm.exe点击运行
快速上手
首先启动一个java程序(本地)
你可以查看本地和远程 Java 应用程序的列表
点击该连接,可以看到该java程序的概述页,包括该进程的PID,主机ip,运行主类,jvm参数,jvm版本,JAVA_HOME等
点击《监视》页面,可以查看该进程的类,cpu,线程活动,堆占用情况。
在这里可以手动《执行垃圾回收》,jvm会对堆进行垃圾回收,它监视和清理不再使用的对象,释放内存空间,避免内存泄漏,以此提高程序性能。
可以看到这个进程设置堆内存为238,026,784B(200多M),最大堆设置为7xxx,堆内存已使用92,573,992B(92M)。
加载的类有15000多个,线程77个。其中活动线程为42个
tips:这里包括了springboot包和项目内部的线程和类。
堆分析
通过点击《堆dump》,可以生成堆的分析文件.hprof,
和很多软件和系统(如windows的bat,ps的psd,Android的apk)都会定制他们的格式一样,你不必管它如何打开,只需导入VisualVM即可查看文件内容
线程分析
通过点击《线程》,可以实时监控该进程的线程,如他们的状态,运行时间。
如线程之间发生了死锁,它会对你提醒。这里模拟一个死锁程序,如下所示Thread1和Thread0会发生死锁现象(可直接copy运行)
package com.xsrmall.portal;
public class DeadlockDemo {
public static void main(String[] args) {
// 创建两个资源对象
Resource resource1 = new Resource("Resource 1");
Resource resource2 = new Resource("Resource 2");
// 创建并启动两个线程
Thread thread1 = new Thread(() -> {
try {
resource1.method1(resource2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
resource2.method2(resource1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
}
// 资源类
static class Resource {
private String name;
public Resource(String name) {
this.name = name;
}
// 获取资源1的锁,然后尝试获取资源2的锁
public synchronized void method1(Resource otherResource) throws InterruptedException {
System.out.println(name + " is holding " + name);
Thread.sleep(100); // 模拟执行业务逻辑
System.out.println(name + " is waiting for " + otherResource.name);
otherResource.method2(this);
System.out.println(name + " released " + otherResource.name);
}
// 获取资源2的锁,然后尝试获取资源1的锁
public synchronized void method2(Resource otherResource) throws InterruptedException {
System.out.println(name + " is holding " + name);
Thread.sleep(100); // 模拟执行业务逻辑
System.out.println(name + " is waiting for " + otherResource.name);
otherResource.method1(this);
System.out.println(name + " released " + otherResource.name);
}
}
}
你可以点击《线程dump》生成线程执行的日志。
如上所示,可以看到死锁线程和发生死锁的具体方法,这可以帮助开发者快速定位死锁问题。
tips: idea在执行这里旁边就有转储线程,
抽样器
类似vmWare的快照,它会对此时进程运行的cpu运行情况进行展示
可以看到这个本地的part方法占用了82%的总进程cpu时间。259s。
也可以对内存进行抽样,不再赘述。
Profiler
Profiler可以进行性能分析。目前笔者不需要这个就不看了,待更新。
事实上,VisualVM的很多执行都是去执行提供的类似jmap,jps,jmx规范等jvm/jdk命令
之后会写一篇jdk和jvm提供的监控jvm的命令或java对象。
参考:
https://www.cnblogs.com/liukaifeng/p/10052647.html
https://www.jianshu.com/p/bb4ba6612392
https://heapdump.cn/article/4481245
https://www.cnblogs.com/baby123/p/11551626.html
https://www.liaoxuefeng.com/wiki/1252599548343744/12823856876...
http://www.oracle.com/technetwork/java/javase/index-138283.html
https://visualvm.github.io/troubleshooting.html?Java_VisualVM
https://www.oracle.com/java/technologies/javase/troubleshooti...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。