JVM 排查问题实战

经过使用 JVM 自带的工具 jstack,jmap,jstat 排查java程序占用内存或cpu负载太高的问题。html

理论部分java

jstack - Prints Java thread stack traces for a Java process, core file, or remote debug server.linux

打印线程堆栈信息centos

命令行使用:服务器

jstack <pid> 获取到 jvm 进程的全部堆栈信息网络

 

jmap - Prints shared object memory maps or heap memory details for a process, core file, or remote debug server.oracle

打印内存中的对象以及堆内存信息eclipse

命令行使用:jvm

jmap -heap <pid> 获取 jvm 进程的堆内存配置以及使用状况工具

jmap -histo <pid> 获取 jvm 进程的在内存中建立的对象数量

jmap -dump:format=b,file=heap.bin <pid> 导出内存文件,这个文件能够经过导入eclipse的MemoryAnalyzer来分析

 

jstat - Monitors Java Virtual Machine (JVM) statistics.

监控 jvm 获取一些统计信息,classloader,compiler,gc相关

命令行使用:

jstat [option] <pid>

参数列表(上面那个 option):

-class:统计class loader行为信息 
-compile:统计编译行为信息 
-gc:统计jdk gc时heap信息 
-gccapacity:统计不一样的generations(不知道怎么翻译好,包括新生区,老年区,permanent区)相应的heap容量状况 
-gccause:统计gc的状况,(同-gcutil)和引发gc的事件 
-gcnew:统计gc时,新生代的状况 
-gcnewcapacity:统计gc时,新生代heap容量 
-gcold:统计gc时,老年区的状况 
-gcoldcapacity:统计gc时,老年区heap容量 
-gcpermcapacity:统计gc时,permanent区heap容量 
-gcutil:统计gc时,heap状况

输出内容:

S0  — Heap上的 Survivor space 0 区已使用空间的百分比 
S0C:S0当前容量的大小 
S0U:S0已经使用的大小 
S1  — Heap上的 Survivor space 1 区已使用空间的百分比 
S1C:S1当前容量的大小 
S1U:S1已经使用的大小 
E   — Heap上的 Eden space 区已使用空间的百分比 
EC:Eden space当前容量的大小 
EU:Eden space已经使用的大小 
O   — Heap上的 Old space 区已使用空间的百分比 
OC:Old space当前容量的大小 
OU:Old space已经使用的大小 
P   — Perm space 区已使用空间的百分比 
OC:Perm space当前容量的大小 
OU:Perm space已经使用的大小 
YGC — 从应用程序启动到采样时发生 Young GC 的次数 
YGCT– 从应用程序启动到采样时 Young GC 所用的时间(单位秒) 
FGC — 从应用程序启动到采样时发生 Full GC 的次数 
FGCT– 从应用程序启动到采样时 Full GC 所用的时间(单位秒) 
GCT — 从应用程序启动到采样时用于垃圾回收的总时间(单位秒),它的值等于YGC+FGC 

 

实战部分

线上环境存在一台服务器(centos 7)cpu使用率一直处于报警状态。

在使用上面3个命令的时候若是出现

Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported vertion are 25.65-b01. Target VM is 25.121-b13

说明工具的版本和启动程序的jvm版本不一致,去oracle官网下载对应版本的jdk工具 传送门,我使用的是jdk1.8 红色部分是须要下载的小的版本 号 即jdk1.8_121

 

使用 top 命令查看cpu使用率

发现一个 pid 为 30996 的cpu使用率特别高,再次使用 top -Hp 30996 命令查看这个进程的线程使用状况

31036 这个就是有问题的线程,使用 printf "%x\n" 31036 转换成16进制(下面有用)

接着就可使用 jstack 命令查看这个线程的堆栈状况 jstack 30996 | grep -A 10 793c (30996是刚才查到有问题的进程 793c为16进制线程号 grep -A 10 为查到793c那一行以后向下再打印10行)

这个 nid=0x793c 的 Thread-14 线程就是问题线程,屡次执行命令查看以后发现代码一直运行在 MasterEngine.java 48行。而后就能够去查看代码了。

看上去在迭代一个map,循环删除。

再看下其余地方

执行 jmap -histo 30996 | head -n 20 看下内存中建立类top20的状况

连续调用4次发现 java.util.concurrent.ConcurrentHashMap$EntryIterator 实例数量在不断增大而后忽然又变小了。猜想是实例被 gc 快速回收掉了。

那么再用 jstat -gcutil 30996 2000 20 看下 gc 执行状况 (2000 200 是指每隔2秒执行一次 一共执行20次)

看YGC这一列(从右边往左第五列),young gc 执行了 2千8百万屡次。

再用 gccapacity 参数看一下堆的状况

只有Eden space (第6列)的大小在发生变化

再看下 jvm 堆的配置状况 jmap -heap 30996

配置了最大堆大小(MaxHeapSize)是 4g,Eden Space 45%, From Space 18%,To Space 0%,Old Generation 73%

再看下服务器负载状况,命令就是一个 w

最后三个数字表示服务器最近 1分钟 5分钟 15分钟 的平均负载,超过cpu核数*1(4核就是4 8核就是8)表示服务器负载很是大。如今这个数字看上去服务器负载比较小仍是可以应对。

总结

结合以上各类迹象,基本能够判定问题就出在对map进行迭代删除的代码,频繁建立 ConcurrentHashMap$EntryIterator 实例,频繁触发 young gc,是致使 cpu 占用太高的缘由。堆大小的占用状况还能够,负载还能接受,内存硬盘都正常(忘记截图了),full gc 次数没有特别多。更加具体的缘由还须要了解代码做者的用意。

因此,只要会使用 jmap jstack jstat 这3个工具,还有一些linux的基本命令(检查磁盘 网络 等等),就能够从容应对由于 代码写得太烂 形成程序异常的状况。

相关文章
相关标签/搜索