JVM调优工具 - Jdk自带命令

178 阅读9分钟

前言

系统日志报OOM了(高内存占用 或 内存泄露), 怎么定位问题?

系统cpu飙升, 哪个功能、哪个接口、哪行代码的问题?

005J4OU5ly1hakplzhxu6g307s07qnoq.gif

避免工作中遇到以上问题 或被灵魂三问时,又似懂非懂、大脑一片空白和无从着手, 对JVM调优常用底层命令,做一个笔记和总结。

一些更直观、高效的调优工具如Jvisualvm、Arthas,本质也是对jdk底层命令的高级封装。所以,先从底层jdk自带命令开始。

PS: 第一次写作。
关于写作,很早就有这个想法, 奈何一直停留在想的阶段,这终于行动了!虽然写的不好, 并且谈不上写作,更多是一个学习笔记,目的是通过输出加深理解和强化记忆,但和昨天的自己比,已经是极大的进步!


1. Jmap

jmap (JVM Memory Map),此命令可以用来查看内存信息, 实例个数及占用的内存大小。

命令: jmap -histo <pid> 输出堆中对象的相关统计信息。

示例: image.png

  • num: 序号

  • insttance: 实例数量

  • bytes: 占有空间大小

  • class name: 类名称, 符号解释:[B: byte[],即byte数组; [C: char[],即char数组; [I: int[],即int数组 ;[[I: int[][],即int二维数组;

2. Jstack

可用于定位线程长时间停顿的原因,如线程死锁、死循环等。用jstack加进程id查找死锁,见如下示例:

package com.jvm.test;

public class DeadLockTest {
    private static Object lock1 = new Object();
    private static Object lock2 = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock1) {
                try {
                    System.out.println("thread1 begin");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("thread1 end");
                }
            }
        }).start();

        new Thread(() -> {
            synchronized (lock2) {
                try {
                    System.out.println("thread2 begin");
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("thread2 end");
                }
            }
        }).start();
        System.out.println("main thread end");
    }
}

代码执行后,一直处于如下状态:

image.png

命令:jstack <pid> 打印线程堆栈信息,检测是否出现死锁:

image.png

  • "Thread-1":线程名
  • prio=31:优先级=31
  • tid=0x00007fd7d080d000:线程id
  • nid=0x3803:线程对应的本地线程标识nid
  • java.lang.Thread.State: BLOCKED:线程状态,BLOCKED为阻塞状态

堆栈信息滚翻到最后,发现:“Found one Java-level deadlock:”,可证明线程出现死锁:

image.png

还可以通过jvisualvm工具自动检测死锁: 终端输入 jvisualvm命令,启动打开Java VisualVM工具:

image.png

VisualVM工具自动检测到死锁: image.png

jstack找出占有cpu最高的线程堆栈信息

代码示例:

package com.jvm.test;

/**
 * 运行此代码,cpu会飙高
 */
public class Math {
    public int compute() {
        int a = 1;
        int b = 2;
        int c = (a + b) * 10;
        return c;
    }
    public static void main(String[] args) {
        Math math = new Math();
        while (true) {
            math.compute();
        }
    }
}

运行后,通过top命令查看cpu占有情况,发现cpu飙升:

image.png

接下来, 通过top + jstack 命令,找到导致cpu飙高的具体线程:

1、使用命令top -p <pid>(发现mac电脑,需要是:top -pid 进程号), 显示需要诊断的java进程的内存情况,pid是对应的java进程号,如上top打印出的:4605, 命令执行后:

image.png

2、按大写H,获取每个线程的内存情况 (这一步,在linux上没问题,但是mac上没反应,因为示例是在本地, 暂时没法截图,mac这个问题后续研究下)

3、找到内存和cpu占有最高的的线程tid(线程id)

4、转为十六进制(因为jstack输出的堆栈信息的线程是用十六进制表示)

5、执行jstack 4605(java进程号)|grep -A 10 步骤4的十六进制,得到线程堆栈信息中此十六进制数的这个线程所在行后面10行,从堆栈信息中可以发现导致cpu飙高的调用的方法

6、查看对应的堆栈信息就找出可能存在问题的代码

3. Jinfo

查看正在运行的Java应用程序的扩展参数信息

查看jvm的参数,命令:jinfo flags <pid>

查看java系统参数,命令jinfo -sysprops <pid>

4. Jstat

jstat命令可用于查看堆内存各部分的使用量,以及加载类的数量

  • 垃圾回收统计

命令:jstat -gc <pid>使用最多,可以评估程序内存使用及GC压力整体情况。

image.png

参数名称含义
S0C第一个幸存区的大小,单位KB
S1C第二个幸存区的大小
S0U第一个幸存区的使用大小
S1U第二个幸存区的使用大小
EC伊甸园区的大小
EU伊甸园区的使用大小
OC老年代大小
OU老年代使用大小
MC方法区大小(元空间)
CCSC压缩类空间大小
CCSU压缩类空间使用大小
YGC年轻代垃圾回收次数
YGCT年轻代垃圾回收消耗时间,单位s
FGC老年代垃圾回收次数
FGCT老年代垃圾回收消耗时间,单位s
GCT垃圾回收消耗总时间,单位s
  • 堆内存统计 命令:jstat -gccapacity <pid>

image.png

参数名称含义
NGCMN新生代最小容量
NGCMX新生代最大容量
NGC当前新生代容量
S0C第一个幸存区大小
S1C第二个幸存区的大小
EC伊甸园区的大小
OGCMN老年代最小容量
OGCMX老年代最大容量
OGC当前老年代大小
OC当前老年代大小
MCMN最小元数据容量
MCMX最大元数据容量
MC当前元数据空间大小
CCSMN最小压缩类空间大小
CCSMX最大压缩类空间大小
CCSC当前压缩类空间大小
YGC年轻代gc次数
FGC老年代GC次数
  • 新生代垃圾回收统计

命令:jstat -gccapacity <pid>

image.png

参数名称含义
S0C第一个幸存区的大小
S1C第二个幸存区的大小
S0U第一个幸存区的使用大小
S1U第二个幸存区的使用大小
TT对象在新生代存活的次数
MTT对象在新生代存活的最大次数
DSS期望的幸存区大小
EC伊甸园区的大小
EU伊甸园区的使用大小
YGC年轻代垃圾回收次数
YGCT年轻代垃圾回收消耗时间
  • 新生代内存统计

命令:jstat -gcnewcapacity <pid>

image.png

参数名称含义
NGCMN新生代最小容量
NGCMX新生代最大容量
NGC当前新生代容量
S0CMX最大幸存1区大小
S0C当前幸存1区大小
S1CMX最大幸存2区大小
S1C当前幸存2区大小
ECMX最大伊甸园区大小
EC当前伊甸园区大小
YGC年轻代垃圾回收次数
FGC老年代回收次数
  • 老年代垃圾回收统计

命令:jstat -gcold <pid>

image.png

参数名称含义
MC方法区大小
MU方法区使用大小
CCSC压缩类空间大小
CCSU压缩类空间使用大小
OC老年代大小
OU老年代使用大小
YGC年轻代垃圾回收次数
FGC老年代垃圾回收次数
FGCT老年代垃圾回收消耗时间
GCT垃圾回收消耗总时间
  • 老年代内存统计

命令:jstat -gcoldcapacity <pid>

image.png

参数名称含义
OGCMN老年代最小容量
OGCMX老年代最大容量
OGC当前老年代大小
OC老年代大小
YGC年轻代垃圾回收次数
FGC老年代垃圾回收次数
FGCT老年代垃圾回收消耗时间
GCT垃圾回收消耗总时间
  • 元数据空间统计 命令:jstat -gcmetacapacity <pid>

image.png

参数名称含义
MCMN最小元数据容量
MCMX最大元数据容量
MC当前元数据空间大小
CCSMN最小压缩类空间大小
CCSMX最大压缩类空间大小
CCSC当前压缩类空间大小
YGC年轻代垃圾回收次数
FGC老年代垃圾回收次数
FGCT老年代垃圾回收消耗时间
GCT垃圾回收消耗总时间
  • 垃圾回收堆内存使用情况总览

命令:jstat -gcutil <pid>

image.png

参数名称含义
S0年轻代中第一个survivor(幸存区)已使用的占当前容量百分比
S1年轻代中第二个survivor(幸存区)已使用的占当前容量百分比
E年轻代中Eden(伊甸园)已使用的占当前容量百分比
O老年代已使用的占当前容量百分比
M元空间已使用的占当前容量百分比
YGC从应用程序启动到采样时年轻代中gc次数
YGCT从应用程序启动到采样时年轻代中gc所用时间(s)
FGC从应用程序启动到采样时老年代代(全gc)gc次数
FGCT老年代垃圾回收消耗时间
GCT从应用程序启动到采样时gc用的总时间(s)

总结

工作中JVM调优常用命令:

命令名称使用场景常用命令
jps查看当前正在运行的Java进程信息,获取进程idjps
jmap例如:系统内存突然飙升,你第一时间怎么办?
这个时候可通过jmap相关命令来分析定位问题
jmap -histo <pid> : 查看内存信息,实例个数以及占用内存大小;
jmap -heap <pid> : 查看堆信息,如:堆内存大小、年轻代内存大小、老年代内存大小、元空间内存大小、新生代中Eden区和S0/S1比例等;
jmap ‐dump:format=b,file=文件名.hprof <pid>: 导出堆内存dump快照;
也可以设置内存溢出时自动导出dump文件:
1. -XX:+HeapDumpOnOutOfMemoryError
2. -XX:HeapDumpPath=(路径)
然后,利用JDK自带的工具jvisualvm、或第三方工具如mat等对dump文件进行分析。
jstack1、例如系统cpu飙升,如何定位?
这个时候就可以利用top+jstack命令来分析问题;
2、死锁问题排查
1、cpu飙升,问题定位步骤:
a. top命令,找出cpu飙高的进程
b.top -p <pid>,显示此进程的内存情况
c. 按大写H,获取每个线程的内存情况
d. 找出cpu占有最高的线程tid
e. 将线程tid转为十六进制
f.执行命令jstack <pid> | grep -A 10 十六进制
g. 查看对应堆栈信息找出可能存在问题的代码

2、死锁问题定位步骤:执行命令:jstack <pid>,看堆栈信息中是否有“Found one Java-level deadlock:”这样的关键信息;(还可以直接通过jvisualvm工具来检测死锁)
jinfo查看正在运行的Java应用程序的扩展参数: 包含 JVM 参数与 Java 系统参数jinfo -flags <pid>: 查看java进程的jvm参数
jinfo -sysprops <pid>: 查看java系统参数
jstat实时监控Java应用程序的资源和性能, 主要包括GC情况、Heap Size资源的使用情况。jstat -gc <pid>jstat -gc <pid> [间隔时间/毫秒] [输出次数]: 最常用,可以评估Java程序内存使用及GC压力整体情况。
jstat -gccapacity <pid>: 堆内存统计
jstat -gcnew <pid>: 新生代垃圾回收统计
jstat -gcnewcapacity <pid>: 新生代内存统计
jstat -gcold <pid>: 老年代垃圾回收统计
jstat -gcoldcapacity <pid>: 老年代内存统计
jstat -gcmetacapacity <pid>: 元数据空间统计
jstat -gcutil <pid>: 显示垃圾回收堆内存使用情况总览。