前言
系统日志报OOM了(高内存占用 或 内存泄露), 怎么定位问题?
系统cpu飙升, 哪个功能、哪个接口、哪行代码的问题?
避免工作中遇到以上问题 或被灵魂三问时,又似懂非懂、大脑一片空白和无从着手, 对JVM调优常用底层命令,做一个笔记和总结。
一些更直观、高效的调优工具如Jvisualvm、Arthas,本质也是对jdk底层命令的高级封装。所以,先从底层jdk自带命令开始。
PS: 第一次写作。
关于写作,很早就有这个想法, 奈何一直停留在想的阶段,这终于行动了!虽然写的不好, 并且谈不上写作,更多是一个学习笔记,目的是通过输出加深理解和强化记忆,但和昨天的自己比,已经是极大的进步!
1. Jmap
jmap (JVM Memory Map),此命令可以用来查看内存信息, 实例个数及占用的内存大小。
命令: jmap -histo <pid> 输出堆中对象的相关统计信息。
示例:
-
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");
}
}
代码执行后,一直处于如下状态:
命令:jstack <pid> 打印线程堆栈信息,检测是否出现死锁:
- "Thread-1":线程名
- prio=31:优先级=31
- tid=0x00007fd7d080d000:线程id
- nid=0x3803:线程对应的本地线程标识nid
- java.lang.Thread.State: BLOCKED:线程状态,BLOCKED为阻塞状态
堆栈信息滚翻到最后,发现:“Found one Java-level deadlock:”,可证明线程出现死锁:
还可以通过jvisualvm工具自动检测死锁:
终端输入 jvisualvm命令,启动打开Java VisualVM工具:
VisualVM工具自动检测到死锁:
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飙升:
接下来, 通过top + jstack 命令,找到导致cpu飙高的具体线程:
1、使用命令top -p <pid>(发现mac电脑,需要是:top -pid 进程号), 显示需要诊断的java进程的内存情况,pid是对应的java进程号,如上top打印出的:4605, 命令执行后:
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压力整体情况。
| 参数名称 | 含义 |
|---|---|
| S0C | 第一个幸存区的大小,单位KB |
| S1C | 第二个幸存区的大小 |
| S0U | 第一个幸存区的使用大小 |
| S1U | 第二个幸存区的使用大小 |
| EC | 伊甸园区的大小 |
| EU | 伊甸园区的使用大小 |
| OC | 老年代大小 |
| OU | 老年代使用大小 |
| MC | 方法区大小(元空间) |
| CCSC | 压缩类空间大小 |
| CCSU | 压缩类空间使用大小 |
| YGC | 年轻代垃圾回收次数 |
| YGCT | 年轻代垃圾回收消耗时间,单位s |
| FGC | 老年代垃圾回收次数 |
| FGCT | 老年代垃圾回收消耗时间,单位s |
| GCT | 垃圾回收消耗总时间,单位s |
- 堆内存统计
命令:
jstat -gccapacity <pid>
| 参数名称 | 含义 |
|---|---|
| NGCMN | 新生代最小容量 |
| NGCMX | 新生代最大容量 |
| NGC | 当前新生代容量 |
| S0C | 第一个幸存区大小 |
| S1C | 第二个幸存区的大小 |
| EC | 伊甸园区的大小 |
| OGCMN | 老年代最小容量 |
| OGCMX | 老年代最大容量 |
| OGC | 当前老年代大小 |
| OC | 当前老年代大小 |
| MCMN | 最小元数据容量 |
| MCMX | 最大元数据容量 |
| MC | 当前元数据空间大小 |
| CCSMN | 最小压缩类空间大小 |
| CCSMX | 最大压缩类空间大小 |
| CCSC | 当前压缩类空间大小 |
| YGC | 年轻代gc次数 |
| FGC | 老年代GC次数 |
- 新生代垃圾回收统计
命令:jstat -gccapacity <pid>
| 参数名称 | 含义 |
|---|---|
| S0C | 第一个幸存区的大小 |
| S1C | 第二个幸存区的大小 |
| S0U | 第一个幸存区的使用大小 |
| S1U | 第二个幸存区的使用大小 |
| TT | 对象在新生代存活的次数 |
| MTT | 对象在新生代存活的最大次数 |
| DSS | 期望的幸存区大小 |
| EC | 伊甸园区的大小 |
| EU | 伊甸园区的使用大小 |
| YGC | 年轻代垃圾回收次数 |
| YGCT | 年轻代垃圾回收消耗时间 |
- 新生代内存统计
命令:jstat -gcnewcapacity <pid>
| 参数名称 | 含义 |
|---|---|
| NGCMN | 新生代最小容量 |
| NGCMX | 新生代最大容量 |
| NGC | 当前新生代容量 |
| S0CMX | 最大幸存1区大小 |
| S0C | 当前幸存1区大小 |
| S1CMX | 最大幸存2区大小 |
| S1C | 当前幸存2区大小 |
| ECMX | 最大伊甸园区大小 |
| EC | 当前伊甸园区大小 |
| YGC | 年轻代垃圾回收次数 |
| FGC | 老年代回收次数 |
- 老年代垃圾回收统计
命令:jstat -gcold <pid>
| 参数名称 | 含义 |
|---|---|
| MC | 方法区大小 |
| MU | 方法区使用大小 |
| CCSC | 压缩类空间大小 |
| CCSU | 压缩类空间使用大小 |
| OC | 老年代大小 |
| OU | 老年代使用大小 |
| YGC | 年轻代垃圾回收次数 |
| FGC | 老年代垃圾回收次数 |
| FGCT | 老年代垃圾回收消耗时间 |
| GCT | 垃圾回收消耗总时间 |
- 老年代内存统计
命令:jstat -gcoldcapacity <pid>
| 参数名称 | 含义 |
|---|---|
| OGCMN | 老年代最小容量 |
| OGCMX | 老年代最大容量 |
| OGC | 当前老年代大小 |
| OC | 老年代大小 |
| YGC | 年轻代垃圾回收次数 |
| FGC | 老年代垃圾回收次数 |
| FGCT | 老年代垃圾回收消耗时间 |
| GCT | 垃圾回收消耗总时间 |
- 元数据空间统计
命令:
jstat -gcmetacapacity <pid>
| 参数名称 | 含义 |
|---|---|
| MCMN | 最小元数据容量 |
| MCMX | 最大元数据容量 |
| MC | 当前元数据空间大小 |
| CCSMN | 最小压缩类空间大小 |
| CCSMX | 最大压缩类空间大小 |
| CCSC | 当前压缩类空间大小 |
| YGC | 年轻代垃圾回收次数 |
| FGC | 老年代垃圾回收次数 |
| FGCT | 老年代垃圾回收消耗时间 |
| GCT | 垃圾回收消耗总时间 |
- 垃圾回收堆内存使用情况总览
命令:jstat -gcutil <pid>
| 参数名称 | 含义 |
|---|---|
| S0 | 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 |
| S1 | 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 |
| E | 年轻代中Eden(伊甸园)已使用的占当前容量百分比 |
| O | 老年代已使用的占当前容量百分比 |
| M | 元空间已使用的占当前容量百分比 |
| YGC | 从应用程序启动到采样时年轻代中gc次数 |
| YGCT | 从应用程序启动到采样时年轻代中gc所用时间(s) |
| FGC | 从应用程序启动到采样时老年代代(全gc)gc次数 |
| FGCT | 老年代垃圾回收消耗时间 |
| GCT | 从应用程序启动到采样时gc用的总时间(s) |
总结
工作中JVM调优常用命令:
| 命令名称 | 使用场景 | 常用命令 |
|---|---|---|
| jps | 查看当前正在运行的Java进程信息,获取进程id | jps |
| 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文件进行分析。 |
| jstack | 1、例如系统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>: 显示垃圾回收堆内存使用情况总览。 |