好好地码代码呢,突然接到线上的告警,说是CPU飙高。然后就稀里哗啦的连上线上的服务器,使用top命令查看了一下CPU情况,发现我们的服务CPU占到600%。然后就开始定位CPU占用高的线程。主要有以下几步:
查询出问题的服务进程id : ps -ef | grep mrf-center | grep -v grep
查询该进程内最耗费CPU的线程 top -Hp pid
转换线程id为16进制 printf "%x\n" 21742
查找特定线程的信息 jstack 21711 | grep 54ee
具体的定位过程可以参考大神你假笨的文章如何定位消耗CPU最多的线程
最后定位出这样一个线程
好了,问题来了,CPU占用过高不是因为业务代码中有CPU密集型任务或者是死循环造成的,而是因为GC太频繁导致的。好,那就验证一下吧。
那gc情况来看可以看出以下几个问题
- 老年代和年轻代快占满了
- 频繁Full GC,但是回收效果不好
- 两个Survivor区有时候都是空的
由此可以猜想频繁Full GC是因为老年代快占满了,而老年代慢了不是因为eden区中的对象因为年龄到了可以晋升为老年代的年龄,而是因为Eden区中有大对象,导致在进行minor gc后,无法回收的对象过大导致没法放进Survivor区,从而只能放进老年代。
所以用一句总结就是,大对象很可怕,朝生夕死的大对象更可怕
那接下来就需要验证自己的猜想,使用jmap命令来dump出jvm的内存
jmap -dump:format=b,file=mm2.dump pid
注意,jmap命令会导致jvm停滞,线上慎用。
下面就是使用工具来分析一下堆内存文件了,推荐使用MAT工具(eclipse 插件)。不过我不太推荐在eclipse中安装这个插件,因为在分析堆内存的时候比较耗费内存,而eclipse又是比较耗费内存的,所以我推荐使用stand-alone的mat工具,可以在此下载
但是,不幸的是你可能打不开这个工具,并且报下面这个错误
Java was started but returned exit code=13
这个问题可以参照下面这个来解决Java was started but returned exit code=13
还有就是,一般堆内存文件都比较大,比较耗费内存。所以为了加载得比较快的话,可以调整一下文件
MemoryAnalyzer.ini中的最大堆内存 。比如我就调整为2G了。-Xmx2048m
好了,完事具备,只欠分析堆内存了。
查看histogram图,然后按照retained heap 排序,可以看到这个PageData占用了将近600M的内存。
为什么就看PageData类型的对象呢,因为这个对象是我们系统中定义的对象,所以优先看
再看一下支配树
好了,发现是因为PageData中的List中装了非常多的HashMap对象。由此可见是因为从数据库中查询了过多的对象导致的。
这样就大概知道了是因为代码中有一个不合理的查询。下面即使定位是哪一个查询了。
其实在看到支配树的数据的时候也大概能知道是哪一个表中的数据了,再结合一下查询就大概知道了。
不过我是从线程栈中看到的(可以通过使用jstack命令来dump出线程栈)
好了,至此就定位出问题了。