问题背景
自从公司测试环境配置为双机负载均衡之后,两台机器的JVM应用进程经常无故自动退出
,每天多达十几二十次
排查过程
首先排查最常见的JVM内存溢出问题
一、进程主动退出
1. 是否JVM本身的OOM导致进程崩溃?
通过观察应用日志,每次退出前,程序看起来都处于正常的运行状态,并没有打印任何会导致程序崩溃的异常信息,更没有堆OOM、方法区OOM、栈溢出等明显的错误信息
故排除
2. JVM的OOM是否会导致进程直接退出?
进一步猜测:是否在程序OOM的时候,已经来不及打印OOM信息就直接退出了?
通过本地OOM验证,以及查阅相关资料确认:
并非每次OOM时,进程都会直接崩溃
而且JVM的OOM退出属于JVM主动行为,进程崩溃前可以做到保留下来相关异常信息,之后再退出进程
而且测试环境的JVM也有配置进程退出时打印堆转储文件,但当时并没有看到保存了任何dump信息
所以到这里基本排除了JVM本身OOM导致主动退出的可能
二、进程被OS杀死
既然JVM不是主动退出的,那他只能被OS杀死
所以排除掉JVM应用层面的故障之后,这时就应该往系统层面去思考了
可从CPU、网络、内存、磁盘等方面来进行排查
首先还是排查可能性最大的内存方面的问题
1. JVM的可使用资源被OS限制住?
通过ulimit -a命令查看OS对进程可使用资源的限制,重点关注内存,发现内存是unlimited的
再查看JVM进程的 /proc/{pid} 路径下的limits文件查看OS对JVM进程的系统限制,也没发现OS在内存方面对JVM有做限制
所以到这里也基本可以排除OS对进程资源使用限制的问题
2. OS保存进程崩溃时的事故现场?
进一步思考:
既然JVM在自己主动崩溃前可以做到保存堆转储文件等各种错误信息
那么OS应该也可以在杀死进程前保留一份信息
可以通过分析这份信息来排查进程崩溃的原因
果然通过 查阅资料 得知
OS的确可以配置在杀死进程前将进程的信息保存入一份core文件
不过这里搞了大半天也没把core文件弄出来….
3. 系统内存资源不足导致进程直接被杀死
core文件弄不出来那就只能继续猜测:既然OS对进程可使用资源没有做限制,那有没有可能OS资源本来就不够?
free、top命令搞起来
哦….
程序可用内存也才7.4G,反手一看-Xmx竟然开到了6144M!
嗯,瞬间就感觉一切要水落石出了~
都知道JVM使用的内存分布,除了最大头的堆,还有线程栈、方法区等
可用内存才7.4G,JVM堆都已经开了6个G,再加上其他的空间,然后OS还有buffer和cache等其他东西要跟JVM抢内存,那内存必然不够啊
三、最终推测及验证
一般来说,OS都是按页给进程分配内存的,而且也不是一次性把所有所需内存分配给进程,都是按需分配,要多少拿多少
所以推测进程崩溃的过程是这样的:
当对象不断创建,堆占用变得越来越大,而这时因为堆开太大6个G,Eden区还没满,还没到触发GC的时候,系统内存占用又即将耗尽,已经没有空闲内存分配给堆了
OS只能杀掉占用内存最多的进程,那不杀JVM还能杀谁….
而通过分析JVM的GC日志,发现基本上每次进程崩溃前最后一次GC,堆的总占用都达到5G+,这也符合了上面的推测
而通过查阅资料,得知了dmesg命令(dmesg -T | grep “(java)”),可查看每次进程被OS杀死的记录
嗯,没得跑了
记录显示Java进程每天都要被kill十几二十次,刚好与测试服务器JVM进程崩溃的次数相吻合
kill的原因也写了是Out of memery
所以,就是JVM进程占用太多的内存,导致系统本身的OOM
真相大白~
问题解决
解决方法
so,知道了问题所在就好办了,把堆内存调小不就可以了
这里先把堆内存调小到4个G,然后每天持续观察JVM进程的运行情况
事后观察闭环
通过jdk自带的jstat工具,日常观察JVM的对象创建速度、YGC次数、YGC频率、YGC耗时,YGC后有多少对象进入Old区,FGC次数,FGC频率、FGC耗时、GC总耗时
同时也通过GC日志观察GC的具体情况
在这之后,测试服务器进程的退出的现象基本没有再出现了
通过 dmesg -T | grep “(java)” 命令也基本没查到JVM进程被OS杀死的记录了
遗留问题
其实实事求是地说,JVM偶尔还是会因为系统OOM被杀死的
这里可以看到自从调小了堆之后,就不会像之前那样,进程每天被杀死十几二十次,但还是会几天发生一次
所以根本原因肯定是系统内存不足的问题,但是到这里没有算根本解决问题,不过已经几乎不影响开发和测试的工作了
这里推测就是系统本身还有其他程序或空间在占用内存,被JVM挤占了
具体原因就涉及到更加深入的OS知识和需要更多的经验了
后续有精力再继续研究这个问题吧累了….
总结
其实这个问题
说复杂吧也不复杂,就是简单的系统内存不够用
说简单吧也费了相当一部分的精力去排查和解决
不过幸运的是最终也算是解决了这个问题
-
总的过程就是:
-
先排除JVM本身OOM导致主动退出进程
-
再排除系统资源限制问题
-
最后定位到系统内存不足导致强行杀死JVM进程
-
-
涉及到的知识有:
-
JVM运行时数据区
-
GC的原理
-
JVM内存溢出
-
GC日志分析技能
-
jdk自带故障排查工具:jinfo、jstat等的使用
-
系统资源限制
-
系统内存分配原理
-
系统kill进程相关知识
-
系统内存OOM相关知识
等等….
-
解决问题就是根据现象不断地进行思考和推测并加以验证的一个过程
这是一件很有趣且有意义的事情,可以将学到的东西加以运用,而且也能从中学到新的东西