在线上偶尔会发生运行一段时间以后, 整个服务器发生卡死; top命令查看后发现CPU被java进程占满; 背后的原因多种多样,可能非常复杂;一般遵循下面的查询套路,可能比较好;
- top 查看CPU 和 内存状况,CPU状况显而易见, 内存状况重点看RES; VRIT时预先分配的内存空间,并不是实际分配内存,可以不用在意; 对于我们的场景就是CPU占满,没有内存溢出,占用CPU的进程就是java进程;
- jstack -F [pid] 检测当前线程是否发生死锁;如果发生死锁基本上就可以打印出死锁信息,如果想要更详细的堆栈信息,就打印出来, 问题就此结束;
-
jstat -gc pid [interval] 根据实践,有一定的可能性,因为代码设计引发了频繁的full gc, 吃掉了所有的CPU;
导致该问题可能的原因是;重点要关注的数据列是FGC, FGCT, 即full gc的次数和 full gc 持续的时间,OC, OU 老年代的容量 和 老年代的使用率;- 内存泄漏(Momery Leak) 需要值得注意,内存泄漏并不一定会导致内存溢出,大量的被引用的无用对象占用了绝大部分的内存空间, 导致实际的老年代空间变得很小;等于是缩小了老年代的大小,但是程序依然可以正常执行;
- 不恰当的手动调用Sytem.gc(), 有些场景下,是需要人为干预gc的时间和时机(对于gc一次特别耗时的场景);所以代码里会显示调用system.gc(); 如果锁定问题,就可以使用--x:+DissableExplicitGC JVM 启动参数,去禁止 显式 调用 gc();
-
jmap -dump:format=b,file=heap.log [pid]如果的确是与full gc有关 查内存泄漏 (Momery Leak) -- 导致内存泄漏的可能性很多,原因都是各种各样的代码设计失误;
- 比如设计一个只读消息队列;每次消费时,只是将访问的指针往0端移动1, 而不是移除元素;那么如果这个消息队列初始化时很大,消费了大量的空间,那么只有到彻底将该消息消费完,空间才有可能释放;
- 多线程并发是错误使用了集合类;该问题常见于跑后台的定时任务;多个同一类型的任务极有可能并发执行;
内存泄漏问题,比较难查,但是也只好一步一步的去分析内存占用情况;EMA(Eclipse Momery Analyzer)是一个分析该问题有用的工具,可以自动生成比较直观的报表;
如果导出的dump依然无法获得有用的结果,那么只好动用 OQL查询,自己去做进一步的分析了;
- top -n 1 -H -p [pid]如果不是full gc的问题,那么头铁硬查;还是回到执行代码的堆栈, 进一步确认是Java进程下哪个线程占用了太多的CPU;jstack [PID] > jstack-output.txt 导出整个堆栈,对照线程的id进行查询; 先确定是哪个线程 --> 再看是哪个方法;