性能监控工具
- java进程JPS
- jstat查看虚拟机运行时信息
- 导出堆到文件jmap
- [JDK自带堆分析工具 jhat](#JDK自带堆分析工具 jhat)
- [可视化性能监控工具Visual VM](#可视化性能监控工具Visual VM)
java进程JPS
jps(JVM Process Status Tool)是 JDK 自带的轻量级命令行工具,默认存放在 $JAVA_HOME/bin 目录下,只有安装 JDK 才会有,单纯的 JRE 环境没有这个命令。它的核心作用是快速列出当前系统中运行的 Java 进程(本质是 JVM 实例),相比 Linux 的 ps -ef | grep java,jps 无需过滤、输出更简洁,且能直接关联 Java 进程的核心信息(如主类、JVM 参数)。

参数说明
-q:只输出进程 ID
-m:输出传入 main 方法的参数
-l:输出完全的包名,应用主类名,jar的完全路径名
-v:输出jvm参数

启动 tomcat

从这个输出中可以看到,当前系统中共存两个Java 应用程序,其中第一个输出Jps就是jps命令本身,这更加证明此命令的本质也是一个Java程序。此外,jps还提供了一系列参数来控制它的输出内容。
参数-q可以指定jps只输出进程ID,而不输出类的短名称:

参数-m可以用于输出传递给Java进程(主函数)的参数:

参数-l可以用于输出主函数的完整路径:

-v表示传递给jvm的参数

jstat查看虚拟机运行时信息
Jstat是JDK自带的一个轻量级工具。全称 Java Virtual Machine statistics monitoring tool,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。

option: 参数选项
-t: 可以在打印的列加上Timestamp列,用于显示系统运行的时间
-h: 可以在周期性数据数据的时候,可以在指定输出多少行以后输出一次表头
vmid: Virtual Machine ID (进程的 pid)
interval: 执行每次的间隔时间,单位为毫秒
count: 用于指定输出多少次记录,缺省则会一直打印
选项option可以由以下值构成

-class:显示ClassLoad的相关信息;
-compiler:显示JIT编译的相关信息;
-gc:显示和gc相关的堆信息;
-gccapacity:显示各个代的容量以及使用情况;
-gcmetacapacity:显示metaspace的大小
-gcnew:显示新生代信息;
-gcnewcapacity:显示新生代大小和使用情况;
-gcold:显示老年代和永久代的信息;
-gcoldcapacity:显示老年代的大小;
-gcutil:显示垃圾收集信息;
-gccause:显示垃圾回收的相关信息(通-gcutil),同时显示最后一次或当前正在发生的垃圾回收的诱因;
-printcompilation:输出JIT编译的方法信息;
-class
如下示例输出Java进程38604的ClassLoader相关信息。每秒钟统计一次信息,一共输出两次:

Loaded : 已经装载的类的数量
Bytes : 装载类所占用的字节数
Unloaded:已经卸载类的数量
Bytes:卸载类的字节数
Time:装载和卸载类所花费的时间
-gc

S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
S0U :年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U :年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
EC :年轻代中Eden(伊甸园)的容量 (字节)
EU :年轻代中Eden(伊甸园)目前已使用空间 (字节)
OC :Old代的容量 (字节)
OU :Old代目前已使用空间 (字节)
MC:metaspace(元空间)的容量 (字节)
MU:metaspace(元空间)目前已使用空间 (字节)
YGC :从应用程序启动到采样时年轻代中gc次数
YGCT :从应用程序启动到采样时年轻代中gc所用时间(s)
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
-gccapacity
可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小

NGCMN :年轻代(young)中初始化的大小 (字节)
NGCMX :年轻代(young)的最大容量 (字节)
NGC :年轻代(young)中当前的容量 (字节)
S0C :年轻代中第一个survivor(幸存区)的容量 (字节)
S1C : 年轻代中第二个survivor(幸存区)的容量 (字节)
EC :年轻代中Eden(伊甸园)的容量 (字节)
OGCMN :old代中初始化(最小)的大小 (字节)
OGCMX :old代的最大容量 (字节)
OGC:old代当前新生成的容量 (字节)
OC :Old代的容量 (字节)
MCMN:metaspace(元空间)中初始化(最小)的大小 (字节)
MCMX :metaspace(元空间)的最大容量 (字节)
MC :metaspace(元空间)当前新生成的容量 (字节)
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC :从应用程序启动到采样时年轻代中gc次数
FGC:从应用程序启动到采样时old代(全gc)gc次数
-gcmetacapacity
metaspace 中对象的信息及其占用量

MCMN:最小元数据容量
MCMX:最大元数据容量
MC:当前元数据空间大小
CCSMN:最小压缩类空间大小
CCSMX:最大压缩类空间大小
CCSC:当前压缩类空间大小
YGC :从应用程序启动到采样时年轻代中gc次数
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
-gcnew
年轻代对象的信息

S0C :年轻代中第一个survivor(幸存区)的容量 (字节)
S1C :年轻代中第二个survivor(幸存区)的容量 (字节)
S0U :年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
S1U :年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
TT:持有次数限制
MTT:最大持有次数限制
DSS:期望的幸存区大小
EC:年轻代中Eden(伊甸园)的容量 (字节)
EU :年轻代中Eden(伊甸园)目前已使用空间 (字节)
YGC :从应用程序启动到采样时年轻代中gc次数
YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
-gcnewcapacity
年轻代对象的信息及其占用量

NGCMN :年轻代(young)中初始化(最小)的大小 (字节)
NGCMX :年轻代(young)的最大容量 (字节)
NGC :年轻代(young)中当前的容量 (字节)
S0CMX :年轻代中第一个survivor(幸存区)的最大容量 (字节)
S0C :年轻代中第一个survivor(幸存区)的容量 (字节)
S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
EC:年轻代中Eden(伊甸园)的容量 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
FGC:从应用程序启动到采样时old代(全gc)gc次数
-gcold
old代对象的信息

MC :metaspace(元空间)的容量 (字节)
MU:metaspace(元空间)目前已使用空间 (字节)
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
OC:Old代的容量 (字节)
OU:Old代目前已使用空间 (字节)
YGC:从应用程序启动到采样时年轻代中gc次数
FGC:从应用程序启动到采样时old代(全gc)gc次数
FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
-gcoldcapacity
old代对象的信息及其占用量

OGCMN :old代中初始化(最小)的大小 (字节)
OGCMX :old代的最大容量 (字节)
OGC :old代当前新生成的容量 (字节)
OC :old代的容量 (字节)
YGC :从应用程序启动到采样时年轻代中gc次数
FGC :从应用程序启动到采样时old代(全gc)gc次数
FGCT :从应用程序启动到采样时old代(全gc)gc所用时间(s)
GCT:从应用程序启动到采样时gc用的总时间(s)
-gccause
下例显示了最近一次GC的原因,以及当前GC的原因:

LGCC:上次GC的原因
GCC:当前GC的原因
导出堆到文件jmap
jmap(JMemory Map)是 JDK 内置的命令行工具,核心作用是与 Java 进程的堆内存交互。可以生成堆转储快照(Dump 文件,二进制格式),用于离线分析内存泄漏、大对象占用、对象分布等问题;查看堆内存配置 / 使用情况、堆内对象统计、ClassLoader 信息、finalizer 队列等,是离线分析 JVM 堆内存的核心工具。
下例使用jmap生成PID为19080的Java程序的对象统计信息,并输出到 tomcat.txt 文件中:
bash
jmap -histo 19080 >d:\tomcat.txt

可以看到,这个输出显示了内存中的实例数量和合计。
jmap 的另一个更为重要的功能是得到Java程序的当前堆快照:
bash
jmap -dump:format=b,file=d:\analyzer\tomcat.hprof 19080

可以通过多种工具分析该堆文件,比如jhat工具,或者Visual VM、MAT等工具。这里使用 MAT 工具打开:

JDK自带堆分析工具 jhat
jhat(Java Heap Analysis Tool)是 JDK 内置的轻量级堆快照分析工具,专门用来解析 jmap 导出的 .hprof 格式堆 Dump 文件。它的核心原理是:将二进制的堆快照文件解析成结构化数据,然后启动一个本地 HTTP 服务器,可通过浏览器以可视化的方式查看堆内存的详细信息(比如对象分布、引用链、实例内容等),无需手动解析复杂的二进制文件。
bash
jhat D:\analyzer\tomcat.hprof

jhat在分析完成后,使用HTTP服务器展示其分析结果,在浏览器中访问 http://127.0.0.1:7000

通过这些链接,开发者可以进一步查看所有类信息(包括Java平台的类)、所有类的实例数量以及实例的具体信息。
通常,导出的堆快照信息非常大,由于信息太多,可能很难通过页面上简单的链接索引找到想要的信息。为此,jhat还支持使用OQL语句对堆快照进行查询。使用OQL查询出当前Java程序中所有java.io.File 对象的路径:

select file.path.value.toString() from java.io.File file

可视化性能监控工具Visual VM
Visual VM(全称 VisualVM)是 JDK 内置的一款免费、开源的多合一可视化故障诊断与性能监控工具,从 JDK 6 Update 7 开始便作为 JDK 的标配组件发布,它的核心价值在于整合了 jps、jstat、jmap、jstack、jhat 等多款命令行工具的功能,并以图形化界面呈现,让开发者和运维人员无需记忆复杂的命令参数,就能直观地监控、分析 Java 应用的运行状态,甚至可以替代功能相对单一的 JConsole。
首先启动一个程序,我这里启动的是 tomcat 程序,双击 apache-tomcat-8.5.57\bin\startup.bat,再打开 jdk 的安装目录双击 C:\Program Files\Java\jdk1.8.0_261\bin\jvisualvm.exe

除了本地连接外,Visual VM 也支持远程JMX连接。
通过修改 Tomcat 启动脚本开启 JMX 远程连接,使得本地 VisualVM 能跨网络监控远程服务器上的 Tomcat 进程,操作流程如下;
打开 Tomcat 安装目录下的 bin/catalina.sh,加入如下启动参数
bash
JAVA_OPTS="-Djava.rmi.server.hostname=123.56.146.124 \
-Dcom.sun.management.jmxremote \
-Dcom.sun.management.jmxremote.port=9999 \
-Dcom.sun.management.jmxremote.rmi.port=9999 \
-Dcom.sun.management.jmxremote.authenticate=false \
-Dcom.sun.management.jmxremote.ssl=false"
-Djava.rmi.server.hostname:用于指定远程服务器的 IP;
-Dcom.sun.management.jmxremote:开启 JMX 远程管理功能,基础开关;
-Dcom.sun.management.jmxremote.port=9999:用于指定 JMX 监听的端口,这个端口要在服务器防火墙中开放,避免被拦截;
-Dcom.sun.management.jmxremote.rmi.port=9999:固定 RMI 的数据传输端口,让 RMI 和 JMX 主端口共用 9999;
JMX 除了主端口,还会用 RMI 传输堆内存、线程等监控数据,默认 RMI 会用随机端口,防火墙无法提前开放,导致连接失败。
-Dcom.sun.management.jmxremote.authenticate:用于开启或关闭关闭 JMX 身份验证;
-Dcom.sun.management.jmxremote.ssl:用于开启或关闭 SSL 加密传输。
开放 9999 端口(需要注意的是 Tomcat 默认端口号为 8080,该端口号也要开启)


重启 Tomcat 使配置生效:
bash
./shutdown.sh && ./startup.sh

下面进行远程连接,首先需要添加远程主机

远程主机右键点击添加JMX 连接...,输入对应的ip和端口号

监控概况
通过 Visual VM,可以查看应用程序的基本情况,比如进程ID、Main Class、启动参数等。
单击Tab页面上的监视页面,即可监控应用程序CPU、堆、永久区、类加载和线程数的总体情况,通过页面上的"执行垃圾回收"和"堆Dump"按钮还可以手工执行Full GC和生成堆快照,如图所示:

内存快照分析功能如图所示:

在顶部的Tab页中,提供了4个基本功能页:概要、类、实例和OQL控制台。
(1) 概要页面展示了当前内存的整体信息,包括内存大小、实例总数、类总数等。
(2) 在类页面中,以类为索引,显示了每个类的实例数占用空间。
(3) 在实例页面中,将显示指定类的所有实例。开发者便可以查看当前内存中,内存数据的实际内容。
(4) OQL控制台提供了更为强大的对象查询功能。有关VisualVM的OQL支持。
使用OQL查询出当前Java程序中所有java.io.File 对象的路径:

如果 Visual VM 在当前程序中找到死锁,则会以十分显眼的方式在线程页面给予提示,如以下死锁程序:
java
import java.util.concurrent.locks.ReentrantLock;
public class DeadLock extends Thread {
protected Object myDirect;
static ReentrantLock south = new ReentrantLock();
static ReentrantLock north = new ReentrantLock();
public DeadLock(Object obj) {
this.myDirect = obj;
if (myDirect == south) {
this.setName("south");
}
if (myDirect == north) {
this.setName("north");
}
}
@Override
public void run() {
if (myDirect == south) {
try {
north.lockInterruptibly();//占用north
try {
Thread.sleep(500);//等待north启动
} catch (Exception e) {
e.printStackTrace();
}
south.lockInterruptibly();//占用south
System.out.println("car to south has passed");
} catch (InterruptedException e1) {
System.out.println("car to south is killed");
} finally {
if (north.isHeldByCurrentThread())
north.unlock();
if (south.isHeldByCurrentThread())
south.unlock();
}
}
if (myDirect == north) {
try {
south.lockInterruptibly();//占用south
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
north.lockInterruptibly();//占用north
System.out.println("car to north has passed");
} catch (InterruptedException e1) {
System.out.println("car to north is killed");
} finally {
if (north.isHeldByCurrentThread())
north.unlock();
if (south.isHeldByCurrentThread())
south.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
DeadLock car2south = new DeadLock(south);//2个线程死锁
DeadLock car2north = new DeadLock(north);
car2south.start();
car2north.start();
Thread.sleep(1000);
}
}

Visual VM 有两个采样器,在"Sampler"页面下,显示了CPU和内存两个性能采样器,用于实时地监控程序信息。CPU采样器可以将CPU占用时间定位到方法,内存采样器可以查看当前程序的堆信息。

通过右键菜单中的"堆Dump"选项,可以立即获得当前应用程序的内存快照,如图所示(相当于jmap命令):

