本文结合深入理解 Java 虚拟机对 JDK 中涉及的若干性能调优及监测工具进行简单使用。
测试代码
首先给出用于测试的 .java 文件
主函数里启动两个线程,每个线程都执行死循环。循环内部对一个 Point 对象的坐标进行随机更新,当循环指定次数后向 List 中追加新的对象,使得堆空间不断增长。
Main.java
public class Main {
public static void main(String[] args) throws RuntimeException {
Thread t1 = new Thread(new MyThread());
t1.start();
Thread t2 = new Thread(new MyThread2());
t2.start();
}
} MyThread.java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/*
一个占用空间很大的类,模拟实际业务
*/
class BigClass{
List<Double> list;
BigClass() {
list = new ArrayList<>();
for (int i=0; i<100000; i++) {
list.add(i*1.0);
}
}
}
class MyThread implements Runnable{
@Override
public void run() {
Point p = new Point(0, 0, "Thread-1");
Random rt = new Random();
int cnt = 0;
while (true) {
int dx = rt.nextInt(10);
int dy = rt.nextInt(10);
if ((dx&1)==1) {
p.add(dx, dy);
}
else {
p.plus(dx, dy);
}
if (cnt%3==0) {
Main.l1.add(new BigClass());
System.out.println("Generated by 1");
}
p.print();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cnt++;
}
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
Point p = new Point(0, 0, "Thread-2");
Random rt = new Random();
int cnt = 0;
while (true) {
int dx = rt.nextInt(10);
int dy = rt.nextInt(10);
if ((dx&1)==1) {
p.add(dx, dy);
}
else {
p.plus(dx, dy);
}
if (cnt%3==0) {
Main.l2.add(new BigClass());
System.out.println("Generated by 2");
}
p.print();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cnt++;
}
}
} Point.java
class Point {
private int x;
private int y;
private String name;
Point(int x, int y, String name) {
this.x = x;
this.y = y;
this.name = name;
}
public void add(int dx, int dy) {
x += dx;
y += dy;
}
public void plus(int dx, int dy) {
x -= dx;
y -= dy;
}
public void print() {
System.out.println(x+ " "+y+" "+" "+name);
}
} jps
jps 命令类似与 linux 的 ps 命令,但是它只列出系统中所有的 Java 应用程序。 通过 jps 命令可以方便地查看 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息。
| 选项 | 作用 |
|---|---|
| -q | 只输出LVMID,省略主类的名称 |
| -m | 输出虚拟机进程启动时传递给主类 main() 函数的参数 |
| -l | 输出主类的全名,如果进程执行的是 jar 包,输出 jar 路径 |
| -v | 输出虚拟机进程启动时 JVM 参数 |
运行 Main 类后在命令行中输入如下命令并观看执行结果:
jstat
jstat 是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据,在没有 GUI 图形界面只提供纯文本控制台环境的服务器上,它是运行期定位虚拟机性能问题的首选工具。jstat 主要工具选项如下:
- -class:监视类装载、卸载数量、总空间以及类装载所耗费的时间
- -gc:监视 java 堆状态,包括 Eden, Survivor, 老年代, 永久代的容量、已用空间、GC 时间等信息
- -gccapacity:同 -gc ,但是更关注堆空间使用的最大最小空间
- -gcutil:同 -gc ,但是更关注已使用空间占总空间的百分比
- -gccause:同 -gcutil ,但是更关注上一次 GC 产生的原因
- -gcnew:监视新生代 GC 情况
- -gcnewcapacity:同 -gcnew ,但是更关注使用到的最大、最小空间
- -gcold:监视老年代 GC 情况
- -gcoldcapacity:同 -gcold,但是更关注使用到的最大、最小空间
- -gcpermcapacity:监视永久代使用的最大、最小空间
运行 Main 类后在命令行中输入如下命令(jstat -参数 pid 时间间隔 采样次数)并观看执行结果:
信息统计中各列含义说明如下:
- S0C:年轻代中第一个survivor(幸存区)的容量 (字节)
- S1C:年轻代中第二个survivor(幸存区)的容量 (字节)
- S0U:年轻代中第一个survivor(幸存区)目前已使用空间 (字节)
- S1U:年轻代中第二个survivor(幸存区)目前已使用空间 (字节)
- EC:年轻代中Eden(伊甸园)的容量 (字节)
- EU:年轻代中Eden(伊甸园)目前已使用空间 (字节)
- OC:Old代的容量 (字节)
- OU:Old代目前已使用空间 (字节)
- PC:Perm(持久代)的容量 (字节)
- PU:Perm(持久代)目前已使用空间 (字节)
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- YGC:从应用程序启动到采样时年轻代中gc次数
- YGCT:从应用程序启动到采样时年轻代中gc所用时间(s)
- FGC:从应用程序启动到采样时old代(全gc)gc次数
- FGCT:从应用程序启动到采样时old代(全gc)gc所用时间(s)
- GCT:从应用程序启动到采样时gc用的总时间(s)
- NGCMN:年轻代(young)中初始化(最小)的大小 (字节)
- NGCMX:年轻代(young)的最大容量 (字节)
- NGC:年轻代(young)中当前的容量 (字节)
- OGCMN:old代中初始化(最小)的大小 (字节)
- OGCMX:old代的最大容量 (字节)
- OGC:old代当前新生成的容量 (字节)
- PGCMN:perm代中初始化(最小)的大小 (字节)
- PGCMX:perm代的最大容量 (字节)
- PGC:perm代当前新生成的容量 (字节)
- S0:年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
- S1:年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
- E:年轻代中Eden(伊甸园)已使用的占当前容量百分比
- O:old代已使用的占当前容量百分比
- P:perm代已使用的占当前容量百分比
- S0CMX:年轻代中第一个survivor(幸存区)的最大容量 (字节)
- S1CMX :年轻代中第二个survivor(幸存区)的最大容量 (字节)
- ECMX:年轻代中Eden(伊甸园)的最大容量 (字节)
- DSS:当前需要survivor(幸存区)的容量 (字节)(Eden区已满)
- TT: 持有次数限制
- MTT : 最大持有次数限制
jinfo
jinfo 的作用是实时查看和调整虚拟机各项参数。当系统崩溃时,jinfo 可以从 core 文件里面知道崩溃的 Java 应用程序的配置信息。选项说明如下:
- no option 输出全部的参数和系统属性
- -flag name 输出对应名称的参数
- -flag [+|-]name 开启或者关闭对应名称的参数
- -flag name=value 设定对应名称的参数
- -flags 输出全部的参数
- -sysprops 输出系统属性
运行 Main 类后在命令行中输入如下命令并观看执行结果:
jmap
jmap 命令用于生成堆存储快照,还可以查询 finalize 执行队列、 Java 对和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。主要选项如下:
- -dump:生成 Java 堆存储快照
- -finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象
- -heap:显示 Java 堆详细信息,比如使用哪种回收器、参数配置、分代状况等
- -histo:显示堆中对象统计信息,包括类、实例数量、合计数量
- -permstat:显示永久代内存状态
- -F:强制选项
运行 Main 类后在命令行中输入如下命令并观看执行结果:
jhat
jhat 是 JDK 内置的工具之一。主要是用来分析 Java 堆的命令,可以将堆中的对象以 Html 的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。运行 Main 类后在命令行中输入如下命令并观看执行结果:
随后在浏览器中访问 http://localhost:7000/ 即可。
jstack
jstack 命令用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈集合,生成线程快照的主要目的在于定位线程长时间停顿的原因,比如线程死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的原因。主要选项如下:
- -F:强制
- -l:除堆栈外,显示关于锁的附加信息
- -m:如果调用本地方法,可以显示C/C++的堆栈
运行 Main 类后在命令行中输入如下命令并观看执行结果:
下面给出一个检测死锁的例子,代码如下:
public class DeadLock {
private static Lock lock1 = new ReentrantLock();
private static Lock lock2 = new ReentrantLock();
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
TimeUnit.SECONDS.sleep(1);
lock2.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
TimeUnit.SECONDS.sleep(1);
lock1.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.setName("mythread1");
t2.setName("mythread2");
t1.start();
t2.start();
}
public static void main(String[] args) {
deathLock();
} 运行这个类后,我们在命令行中观看其输出如下:
首先,使用 -l 参数输出了关于死锁的信息。接下来定位到两个线程的信息输出位置:

京公网安备 11010502036488号