写点什么

JVM 性能诊断工具

作者:Ayue、
  • 2021 年 12 月 04 日
  • 本文字数:4703 字

    阅读完需:约 15 分钟

JVM 性能诊断工具

前言

在开发,运行 Java 应用时,难免会遇到应用运行性能低效,内存泄露等问题,那么我们就需要借助分析工具去分析,优化应用系统,也就是常说的性能调优,而 JDK 自带的诊断工具可以有效的帮助我们快速定位问题。如:jps,jstack,jinfo等。



可以看到,这些工具在 windows 上,就是 exe。而在 linux 中,一般自带了 OpenJDK,一般情况下 JPS 等命令不能用,要么选择去安装 JPS 等插件,要么把 OpenJDK 卸载,去重新安装 Oracle 的 JDK,个人建议是后者,毕竟后者只需要安装一次就包含了所有。


需要注意的是,这些工具大部分都是命令行工具,但也有可视化工具,如jconsolvisualvm

命令行工具

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 去查看,如下:


可视化工具

jvisualvmjconsole都是一个基于图形化界面的、可以查看本地及远程的 JAVA GUI 监控工具,可以认为jvisualvmjconsole的升级版。jvisualvm是一个综合性的分析工具,可以认为其整合了jstack、jmap、jinfo等众多调试工具的功能,并以图形界面展示。


但是一般来说,我们的服务都是在 linux 上面,且是不支持图形化界面的,所以这两个可以在本地玩一下。

jconsole

一般来说,在 JDK 安装目录下的 bin 文件夹下面,可以直接点击启动或命令启动。虽然 jconsole 可以远程连接,但一般来说为了安全是不会对外开发,所以也只能在本地测试一下。


语法:jconsole



jvisualvm

一般来说,在 JDK 安装目录下的 bin 文件夹下面,可以直接点击启动或命令启动。


语法:jvisualvm



查看堆栈信息



检测死锁:


总结

尽管 JDK 提供了这么多的工具来供我们使用,但是大多数情况是需要我们去选择使用的,一般来说在生产环境主要从这 3 个方面去考虑。


  1. 生产服务器推荐开启

  2. -XX:-HeapDumpOnOutOfMemoryError: 默认关闭,建议开启,在 java.lang.OutOfMemoryError 异常出现时,输出一个 dump 文件,记录当时的堆内存快照。

  3. -XX:HeapDumpPath=./java_pid<pid>.hprof :用来设置堆内存快照的存储文件路径,默认是 java 进程启动位置。

  4. 调优之前开启、调优之后关闭

  5. -XX:+PrintGC :调试跟踪,打印简单的 GC 信息参数。

  6. -XX:+PrintGCDetails, +XX:+PrintGCTimeStamps :打印详细的 GC 信息

  7. -Xlogger:logpath :设置 gc 的日志路径,如: -Xlogger:log/gc.log, 将 gc.log 的路径设置到当前目录的 log 目录下。

  8. 应用场景:

  9. 将 gc 的日志独立写入日志文件,将 GC 日志与系统业务日志进行了分离,方便开发人员进行追踪分析。

  10. 考虑使用

  11. -XX:+PrintHeapAtGC: 打印堆信息 。

  12. 应用场景:

  13. 获取堆在每次垃圾回收前后的使用状况 。

  14. -XX:+TraceClassLoading,-XX:+TraceClassUnloading:可以跟踪类加载和卸载的情况,可以用来排查 class 的冲突问题。

  15. 应用场景:

  16. 在系统控制台信息中看到 class 加载的过程和具体的 class 信息,可用以分析类的加载顺序以及是否可进行精简操作。

  17. 如果碰到经常 Full GC 的情况,但是老年代空间使用的却不多,年轻代 GC 后的情况也很正常,同时也不存在突然大对象的情况,但是元空间却一直递增,那么可以考虑下是不是使用了反射等手段导致元空间加载的类太多了,导致元空间爆满触发 Full GC,那么此时就可以加上这两个参数,看下类加载和卸载的情况,确定下是不是有哪些类反复被生成和加载,找到相应的类,然后跟踪到代码里,排除问题。

  18. -XX:+DisableExplicitGC:禁止在运行期显式地在代码中调用 System.gc()

发布于: 4 小时前阅读数: 8
用户头像

Ayue、

关注

还未添加个人签名 2019.10.16 加入

学习知识,目光坚毅

评论

发布
暂无评论
JVM 性能诊断工具