这是我参与8月更文挑战的第15天,活动详情查看: 8月更文挑战
在生产中常见的问题如下:
- CPU飙升导致系统不可用或tps降低;
- Minor GC次数过多;
- Full GC次数过多;
- Full GC时间过长;
- 内存泄漏、内存溢出;
- 死锁等;
恰当地使用虚拟机故障处理、分析的工具可以提升我们分析数据、定位并解决问题的效率。
jps
查看虚拟机进程。
jmp
用于查看内存信息和生成堆存储快照(一般称为headdump或dump文件)。
查看实例数和占用内存大小
使用jmap -histo 1695 > ./jmp.txt打印演示所用程序内存信息到jmp.txt文件中,部分截图如下:
- num:序号
- Instances:实例数量
- bytes:占用空间大小
- class name:类名称([C is a char[],[S is a short[],[I is a int[],[B is a byte[],[[I is a int[][])
查看堆信息
使用 jhsdb jmap --heap --pid 1695 > ./jmap_heap.txt 查看堆信息,打印到jmap_heap.txt文件中,部分截图如下所示:
首先是Heap Configuration,即堆配置信息:
- MinHeapFreeRatio:JVM堆最小空闲比率。对应JVM参数为
-XX:MinHeapFreeRatio。 - MaxHeapFreeRatio:JVM堆最小空闲比率。对应JVM参数为
-XX:MaxHeapFreeRatio。 - MaxHeapSize:堆的最大大小。对应JVM参数为
-XX:MaxHeapSize。 - NewSize:堆的‘新生代’的默认大小。对应JVM参数为
-XX:NewSize。 - MaxNewSize:堆的‘新生代’的最大大小。对应JVM参数为
-XX:MaxNewSize。 - OldSize:堆的‘老生代’的大小。对应JVM参数为
-XX:OldSize。 - NewRatio:‘新生代’和‘老生代’的大小比率。对应JVM参数为
-XX:NewRatio。 - SurvivorRatio:年轻代中Eden区与Survivor区的大小比值。对应JVM参数为
-XX:SurvivorRatio。 - SurvivorRatio:年轻代中Eden区与Survivor区的大小比值。对应JVM参数为
-XX:SurvivorRatio。 - MetaspaceSize:元空间的默认大小。对应JVM参数为
-XX:MetaspaceSize。 - CompressedClassSpaceSize:类指针压缩空间的默认大小。对应JVM参数为
-XX:CompressedClassSpaceSize。 - MaxMetaspaceSize:元空间的最大大小。对应JVM参数为
-XX:MaxMetaspaceSize。 - G1HeapRegionSize:使用G1垃圾收集器的时候,堆被分割的大小(即每个region的大小)。对应JVM参数为
-XX:G1HeapRegionSize。
接着就是Heap Usage,即堆使用情况,包括G1收集器的内存划分,新生代区域、Eden区和Survivor区以及老年代区域分配使用情况等。
导出dump文件
使用jmap -dump:format=b,file=javagoing.hprof 1695导出dump文件。
可以使用jvisualvm工具来分析这个dump文件。
jstack
Java堆栈跟踪工具。用于生成虚拟机当前时刻的线程快照(一般称为threaddump或javacore)。线程快照就是当前虚拟机内每一条正在执行的方法堆栈的集合,生成线程快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等。
死锁检测
一个简单的死锁例子:
public class DeadLock {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
System.out.println("thread1 begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("thread1 end");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
System.out.println("thread2 begin");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("thread2 end");
}
}
}).start();
}
}
启动程序,发现进入死锁状态,使用jps命令查看进程id,然后使用jstack+进程id即可查看死锁信息。如jstack 2862>deadlock.txt,生成如下信息:
还可以使用jvisualvm自动检测死锁:
找出CPU最高的堆栈信息
- 使用命令
top -p pid,显示你的java进程的内存情况,pid是你的java进程号,比如2862。 - 按H,获取每个线程的内存情况。
- 找到内存和cpu占用最高的线程tid,比如2862。
- 转为十六进制得到b2e,此为线程id的十六进制表示。
- 执行jstack4977|grep-b2e,得到线程堆栈信息中1371这个线程所在行的后面10行。
- 查看对应的堆栈信息找出可能存在问题的代码。
jnfo
Java配置信息工具。查看正在运行的Java程序的扩展参数。
查看jvm参数
使用jinfo -flags pid,如下所示:
查看Java系统参数
使用jinfo -sysprops pid命令。
jstat
虚拟机信息监视工具。用于监视虚拟机各种运行状态信息,可以显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据。
垃圾回收统计
使用jstat -gc pid命令可以查看gc信息,用于评估程序内存使用及GC压力整体情况。
- S0C:第一个幸存区的大小
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:方法区大小(元空间)
- MU:方法区使用大小
- 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 -gcnew 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:老年代回收次数
还可以查看老年代垃圾回收、内存统计以及元空间内存统计等,就不再演示了。有了这些数据就可以采用之前介绍过的优化思路,先给自己的系统设置一些初始性的JVM参数,比如堆内存大小,年轻代大小,Eden和Survivor的比例,老年代的大小,大对象的阈值,大龄对象进入老年代的阈值等。
总结
以上介绍了JVM提供的几个重要的故障处理工具,希望能够给大家解决问题提供一些帮助。