JDK自带性能诊断工具
JVM往期文章
- 【JVM进阶之路】内存结构(一)
- 【JVM进阶之路】玩转JVM中的对象(二)
- 【JVM进阶之路】垃圾回收机制和GC算法之三色标记(三)
- 【JVM进阶之路】类加载和双亲委派模型(四)
- 【JVM进阶之路】方法调用的底层实现之面试必问重载与重写的区别(五)
前言
在开发,运行Java应用时,难免会遇到应用运行性能低效,内存泄露等问题,那么我们就需要借助分析工具去分析,优化应用系统,也就是常说的性能调优,而JDK自带的诊断工具可以有效的帮助我们快速定位问题。如:jps,jstack,jinfo
等。
可以看到,这些工具在 windows 上,就是 exe。而在 linux 中,一般自带了 OpenJDK,一般情况下 JPS 等命令不能用,要么选择去安装 JPS 等插件,要么把 OpenJDK 卸载,去重新安装 Oracle 的 JDK,个人建议是后者,毕竟后者只需要安装一次就包含了所有。
需要注意的是,这些工具大部分都是命令行工具,但也有可视化工具,如jconsol
,visualvm
。
命令行工具
jps
java提供的一个显示当前所有java进程pid的命令,它的作用是显示当前系统的java进程情况及进程id。我们可以通过它来查看我们到底启动了几个java进程(因为每一个java程序都会独占一个java虚拟机实例)。
语法:jps [options] <pid>
[options]
选项:官方文档
-q
:仅仅显示进程。-m
:输出主函数传入的参数。-l
:输出应用程序主类完整package
名称或jar
完整名称。-v
:列出 JVM参数。
jstat
是用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。简单来说就是可以查看堆内存各部分的使用量,以及加载类的数量。
语法:jstat [options] [vmid] [间隔时间/毫秒] [查询次数]
[options]
选项:官方文档
-class
: 显示有关类加载器行为的统计信息。-compiler
:显示有关 Java HotSpot VM Just-in-Time(JIT) 编译器行为的统计信息。-gc
:显示有关垃圾收集堆行为的统计信息。-gccapacity
:显示各区大小。-gccause
:最近一次 GC 统计和原因。-gcnew
:显示新生代行为的统计信息。-gcnewcapacity
: 显示有关新生代大小及其对应空间的统计信息。-gcold
:显示有关老年代行为的统计信息和元空间统计信息。-gcoldcapacity
:显示老年代大小。-gcmetacapacity
:显示有关元空间大小的统计信息。-gcutil
:显示有关垃圾收集统计信息的摘要。-printcompilation
:显示 Java HotSpot VM 编译方法统计信息。
如,统计GC信息:jstat -gc pid
说明:
S0C:第一个幸存区(From 区)的大小
S1C:第二个幸存区(To 区)的大小
S0U:第一个幸存区的使用大小
S1U:第二个幸存区的使用大小
EC:伊甸园(Eden)区的大小
EU:伊甸园(Eden)区的使用大小
OC:老年代大小
OU:老年代使用大小
MC:方法区大小
MU:方法区使用大小
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
jinfo
可以用来查看正在运行的 Java 进程运行的 JVM 参数,包括Java System属性和JVM命令行参数;也可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息。
语法:jinfo [option] pid
[options]
选项:官方文档
–sysprops
:可以查看由 System.getProperties()取得的参数 。–flag
:未被显式指定的参数的系统默认值 。–flags
:显示虚拟机的参数。
如查看 JVM 参数:
同时还可以动态修改 JVM 的参数,比如我们写这样一个类:
public class StopWorld {
/*不停往list中填充数据*/
//就使用不断的填充 堆 -- 触发GC
public static class FillListThread extends Thread {
List<byte[]> list = new LinkedList<>();
@Override
public void run() {
try {
while (true) {
if (list.size() * 512 / 1024 / 1024 >= 990) {
list.clear();
System.out.println("list is clear");
}
byte[] bl;
for (int i = 0; i < 100; i++) {
bl = new byte[512];
list.add(bl);
}
Thread.sleep(1);
}
} catch (Exception e) {
}
}
}
/*每100ms定时打印*/
public static class TimerThread extends Thread {
public final static long startTime = System.currentTimeMillis();
@Override
public void run() {
try {
while (true) {
long t = System.currentTimeMillis() - startTime;
//System.out.println(t/1000+"."+t%1000);
Thread.sleep(100); //0.1s
}
} catch (Exception e) {
}
}
}
public static void main(String[] args) {
//填充对象线程和打印线程同时启动
FillListThread myThread = new FillListThread(); //造成GC,造成STW
TimerThread timerThread = new TimerThread(); //时间打印线程
myThread.start();
timerThread.start();
}
}
默认情况下GC日志是关闭的,控制台没有任何输出:
通过jinfo
打开:
jinfo -flag +PrintGC pid
关闭 GC 日志的话同理:
jinfo -flag -PrintGC pid
同时可以查看是否开启 GC 日志的打印:
jinfo -flag PrintGC pid
jmap
获得运行中的JVM的堆的快照(一般称为 heapdump 或 dump 文件),从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小。
语法:jmap [option] pid
[options]
选项:官方文档
–heap
:打印JVM内存整体使用情况 。-histo
:打印每个 class 的实例数目,内存占用,类全名信息。-histo[:live]
:只统计活的对象数量,即在统计之前会触发一次Full GC。
jmap -dump:live,format=b,file=filename <pid>
:生成的堆转储快照。format
:格式,一般是byte。file
:生产文件,可指定目录。
jhat
Sun JDK 提供 jhat(JVM Heap Analysis Tool)命令与 jmap 搭配使用,用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等。
语法:jhat [option] 文件
[options]
选项:官方文档
分析上个命令生成的文件:
访问http://localhost:7000/
但这种一般不推荐,毕竟占用服务器的资源,比如一个文件就有 1 个 G 的话就需要大约吃一个 1G 的内存资源,如上面生成的文件就有几百兆了。
jstack
主要用于调试 java 程序运行过程中的线程堆栈信息,可以用于检测死锁,进程耗用cpu过高报警问题的排查。
语法:jstack [option] pid
[options]
选项:官方文档
如下面这个例子:演示死锁的产生
/**
* 类说明:演示死锁的产生,2个线程分别持有自己的锁,在不释放的情况下又想去获取对方的锁
*/
public class NormalDeadLock {
private static Object lock1 = new Object();//第一个锁
private static Object lock2 = new Object();//第二个锁
//第一个拿锁的方法
private static void lock1Do() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (lock1) {
System.out.println(threadName + " get lock1");
Thread.sleep(100);
synchronized (lock2) {
System.out.println(threadName + " get lock2");
}
}
}
//第二个拿锁的方法
private static void lock2Do() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (lock2) {
System.out.println(threadName + " get lock2");
Thread.sleep(100);
synchronized (lock1) {
System.out.println(threadName + " get lock1");
}
}
}
//子线程代表lock2
private static class Lock2 extends Thread {
private String name;
public Lock2(String name) {
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try {
lock1Do();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//主线程代表lock1
Thread.currentThread().setName("lock1");
Lock2 lock2 = new Lock2("lock2");
lock2.start();
lock2Do();
}
}
这个时候通过jstack去查看,如下:
可视化工具
jvisualvm
和jconsole
都是一个基于图形化界面的、可以查看本地及远程的JAVA GUI监控工具,可以认为jvisualvm
是jconsole
的升级版。jvisualvm
是一个综合性的分析工具,可以认为其整合了jstack、jmap、jinfo
等众多调试工具的功能,并以图形界面展示。
但是一般来说,我们的服务都是在linux上面,且是不支持图形化界面的,所以这两个可以在本地玩一下。
jconsole
一般来说,在JDK安装目录下的bin文件夹下面,可以直接点击启动或命令启动。虽然jconsole可以远程连接,但一般来说为了安全是不会对外开发,所以也只能在本地测试一下。
语法:jconsole
jvisualvm
一般来说,在JDK安装目录下的bin文件夹下面,可以直接点击启动或命令启动。
语法:jvisualvm
查看堆栈信息
检测死锁:
总结
尽管JDK提供了这么多的工具来供我们使用,但是大多数情况是需要我们去选择使用的,一般来说在生产环境主要从这3个方面去考虑。
生产服务器推荐开启
-XX:-HeapDumpOnOutOfMemoryError
: 默认关闭,建议开启,在 java.lang.OutOfMemoryError 异常出现时,输出一个 dump 文件,记录当时的堆内存快照。-XX:HeapDumpPath=./java_pid<pid>.hprof
:用来设置堆内存快照的存储文件路径,默认是 java 进程启动位置。调优之前开启、调优之后关闭
-XX:+PrintGC
:调试跟踪,打印简单的 GC 信息参数。-XX:+PrintGCDetails, +XX:+PrintGCTimeStamps
:打印详细的 GC 信息-Xlogger:logpath
:设置 gc 的日志路径,如:-Xlogger:log/gc.log
, 将gc.log
的路径设置到当前目录的 log 目录下。应用场景:
- 将 gc 的日志独立写入日志文件,将 GC 日志与系统业务日志进行了分离,方便开发人员进行追踪分析。
考虑使用
-XX:+PrintHeapAtGC
: 打印堆信息 。应用场景:
- 获取堆在每次垃圾回收前后的使用状况 。
-XX:+TraceClassLoading,-XX:+TraceClassUnloading
:可以跟踪类加载和卸载的情况,可以用来排查 class 的冲突问题。应用场景:
- 在系统控制台信息中看到 class 加载的过程和具体的 class 信息,可用以分析类的加载顺序以及是否可进行精简操作。
- 如果碰到经常Full GC的情况,但是老年代空间使用的却不多,年轻代GC后的情况也很正常,同时也不存在突然大对象的情况,但是元空间却一直递增,那么可以考虑下是不是使用了反射等手段导致元空间加载的类太多了,导致元空间爆满触发Full GC,那么此时就可以加上这两个参数,看下类加载和卸载的情况,确定下是不是有哪些类反复被生成和加载,找到相应的类,然后跟踪到代码里,排除问题。
-XX:+DisableExplicitGC
:禁止在运行期显式地在代码中调用System.gc()
。
还没有评论,来说两句吧...