JVM 常用工具简介及示例

168 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情

jps:虚拟机进程状况工具

可以列出正在运行的虚拟机进程,显示虚拟机执行主类名称以及进程的本地虚拟机唯一 ID。

命令格式:

jps [选项] [主机名]

主要选项:

选项作用
-q只输出进程 ID
-m输出虚拟机进程启动时传递给 main() 方法的参数
-l输出主类的全名,如果进程执行的是 jar 包,那么输出其路径
-v输出虚拟机进程启动时的 JVM 参数

jstat:虚拟机统计信息监视工具

可以监控虚拟机各种运行状态信息,如虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据。

命令格式:

jstat [选项 进程ID [ 查询间隔[秒|毫秒(默认)] [查询次数] ] ]

主要选项:

选项作用
-class监视类加载、卸载数量、总空间以及类装载所耗费的时间
-gc监视 Java 堆状况,包括 Eden、Survivor、老年代、元空间等的容量,已用空间和垃圾收集时间合计等信息
-gccapacity与 -gc 基本相同,主要关注堆中各个区域使用到的最大、最小空间
-gcutil与 -gc 基本相同,主要关注已用空间占总空间的百分比
-gccause与 -gcutil 一样,额外输出上次垃圾收集的原因
-gcnew监视新生代垃圾收集情况
-gcnewcapacity与 -gcnew 基本相同,主要关注使用到的最大、最小空间
-gcold监视新生代垃圾收集情况
-gcoldcapacity与 -gcold 基本相同,主要关注使用到的最大、最小空间
-compiler输出即时编译器编译过的方法、耗时等信息
-printcompilation输出已经被即时编译的方法

jinfo:Java 配置信息工具

可以实时查看和调整虚拟机各项参数(即 VM 参数)。

命令格式:

jinfo [选项] pid

windows 只提供了 -flag 选项

示例:

jinfo -flag xxxx 1234;

jmap:Java 内存映像工具

jmap 命令用于生成堆存储快照,可以用来查询 finalize 执行队列、Java 堆和方法区的详细信息(空间使用率、使用哪种收集器等)。

命令格式:

jmap [选项] 进程ID

应用示例

jmap -dump:format=b,file=IDEA.bin 11111

windows 只能使用部分选项,主要选项如下:

选项作用
-dump生成 Java 堆存储快照
格式为 -dump:[live,]format=b,file=<文件名>
live 子参数说明是否只转储存活的对象
-histo显示堆中对象统计信息,包括类、实例数量、合计容量

jhat:虚拟机堆快照分析工具

与 jmap 搭配使用,分析 jmap 生成的堆转储快照。

实际情况中应用较少,一般使用 VisualVM,Eclipse Memory Analyzer,IBM HeapAnalyzer 等工具分析快照文件。

命令格式:

jhat <快照文件路径>

执行完毕后访问localhost:7000查看分析结果。

jstack:Java 堆栈跟踪工具

可以生成当前虚拟机当前时刻的线程快照。

线程快照即每一条线程正在执行的方法堆栈的集合,如果线程死循环、死锁,都可以使用该工具检查。

命令格式:

jstack [选项] 进程ID

主要选项:

选项作用
-F当正常输出的请求不被响应时,强制输出线程堆栈
-l显示关于锁的附加信息
-m如果有使用到本地方法,可以显示本地方法栈

jconsole:Java监视与管理控制台

可视化工具,可以查看堆内存、虚拟机栈的使用情况。

使用:

jconsole 启动即可。

启动界面:

image-20220807114827318.png

选择对应的 java 进程即可以查看详细的信息,可以查看堆内存使用情况,排查线程是否出现死锁等等。

代码示例

我们使用一份测试代码使用上面提到的工具,代码的主体功能为:

  1. 两个线程争抢锁,发生死锁现象
  2. 不断创建对象,促使堆内存内存紧张,触发垃圾回收

VM 参数说明:

  • -Xmx20m -Xms20m:指定堆内存的最大大小和初始大小为 20m
  • -Xmn10m:指定新生代内存为 10m
  • -XX:+PrintGCDetails -verbose:gc 触发垃圾回收时,打印详情
// VM 参数 -Xmx20m -Xms20m -Xmn10m -XX:+PrintGCDetails -verbose:gc
public class TestTools {

    private static MyLock lockA = new MyLock();
    private static MyLock lockB = new MyLock();

    public static void main(String[] args) {
        testStack();
        testHeap();
    }

    // 测试线程死锁,使用工具排查
    private static void testStack() {
        new Thread(()->{
            synchronized (lockA) {
                System.out.println(Thread.currentThread().getName() + "拿到锁A");
                try {
                    Thread.sleep(5000);
                    synchronized (lockB) {
                        System.out.println(Thread.currentThread().getName() + "拿到锁B");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "拿到锁B");
                synchronized (lockA) {
                    System.out.println(Thread.currentThread().getName() + "拿到锁A");
                }
            }
        }).start();
    }

    // 测试堆内存回收,使用工具查看内存占用情况
    private static void testHeap() {
        try {
            System.in.read();
            System.out.println("测试堆内存");
            while (true) {
                byte[] bytes = new byte[1024];
                Thread.sleep(1);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

class MyLock {

}

测试步骤:

  1. 启动程序,使用 jps 指令查看 Java 进程的进程 ID
jps
100336 
61556 TestTools
67148 Launcher
95516 Jps
  1. 使用 jstat 查看堆内存占用情况
jstat -gcutil 61556
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  62.95   0.00  17.38  19.94      0    0.000     0    0.000    0.000
# 分别表示 from survivor, to survivor, eden, 老年代, 元空间, 压缩使用比例, Minnor GC回收次数, Minnor GC回收总耗时, FullGC 次数, FullGC回收总耗时, 所有GC总耗时
  1. 使用 jinfo 命令查看相关参数是否启用
jinfo -flag PrintGCDetails 61556
-XX:+PrintGCDetails				# + 表示已经启用
# 如果命令为 jinfo 61556 ,终端会输出更多的参数信息,如下,包括 VM 版本号,jDK 版本等等
Attaching to process ID 61556, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.321-b07
Java System Properties:

java.runtime.name = Java(TM) SE Runtime Environment
java.vm.version = 25.321-b07
sun.boot.library.path = D:\program\Java\jdk1.8.0_321\jre\bin
java.vendor.url = http://java.oracle.com/
java.vm.vendor = Oracle Corporation
path.separator = ;
file.encoding.pkg = sun.io
java.vm.name = Java HotSpot(TM) 64-Bit Server VM
sun.os.patch.level =
sun.java.launcher = SUN_STANDARD
user.script =
user.country = CN
.......
  1. 使用 jmap 转储堆内存
jmap -dump:format=b,file=TestTools.bin 61556
Dumping heap to E:\Program\Java\TestDemo\TestTools.bin ...
Heap dump file created
  1. 使用 jhat 分析堆内存映像
jhat TestTools.bin
Dumping heap to E:\Program\Java\TestDemo\TestTools.bin ...
Heap dump file created
PS E:\Program\Java\TestDemo> jhat TestTools.bin
Reading from TestTools.bin...
Dump file created Sun Aug 07 12:09:21 CST 2022
Snapshot read, resolving...
Resolving 47141 objects...
Chasing references, expect 9 dots.........
Eliminating duplicate references.........
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

访问 http://localhost:7000 查看

image-20220807121044331.png

  1. 使用 jstack 工具
jstack -l 61556
2022-08-07 12:12:08
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.321-b07 mixed mode):
## 打印出很多信息,我们直接查看信息最后部分,有产生死锁的位置和原因,可以很快地排查到是源代码的第几行

Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x00000258fd3d07a8 (object 0x00000000ff7aa638, a MyLock),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00000258fd3cf258 (object 0x00000000ff7aa648, a MyLock),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at TestTools.lambda$testStack$1(TestTools.java:42)	## 问题出现在源代码第 42 行 和 31 行
        - waiting to lock <0x00000000ff7aa638> (a MyLock)
        - locked <0x00000000ff7aa648> (a MyLock)
        at TestTools$$Lambda$2/1078694789.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)
"Thread-0":
        at TestTools.lambda$testStack$0(TestTools.java:31)
        - waiting to lock <0x00000000ff7aa648> (a MyLock)
        - locked <0x00000000ff7aa638> (a MyLock)
        at TestTools$$Lambda$1/1324119927.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:750)

Found 1 deadlock.

  1. 使用 jconsole 工具查看

启用工具后,选择对应的进程,并让 testHeap() 继续运行,查看内存使用情况

image-20220807122148378.png

可以看到后面发生了一次 GC 后内存占用减少了

现在排查死锁,点击【线程】标签页,再点击检测死锁:

image-20220807122253902.png

image-20220807122253902.png

image-20220807122344414.png

同样可以排查死锁。

小结

Java 提供了多种虚拟机工具帮助我们查看虚拟机的运行状况,在出现问题时可以有效地排查问题所在,在远程连接服务器的情况下,我们可以使用命令行工具排查问题,但多数时候,我们可以使用可视化工具来排查,例如 jconsole 可以更高效、方便地查看情况。

此外,还有其他的工具可以使用,例如 Eclipse 的 Eclipse Memory Analyzer 可视化工具。