服务器上jvm挂了,最快的排查路径是:先确认进程状态和系统资源,再看jvm日志和操作系统日志,最后结合dump文件定位根因。这个过程听起来像侦探破案,但按顺序排查,往往能在几分钟内找到线索。
先分清到底是“真挂了”还是“假死了”
很多人一看到jvm没响应就慌,第一反应是重启,但重启只治标不治本,下次还得挂,动手之前,先花一分钟判断症状:
进程消失(真挂了)
- 用
ps -ef | grep java查看进程是否还在 - 用
top确认java进程的CPU和内存占用情况
进程还在但卡死(假死)
jstack命令可以打印线程快照,如果命令执行超时或直接报错,大概率是假死- 应用端口无法访问,但是
ping能通,说明操作系统层面的网络是通的
业内专家指出,生产环境里jvm假死的出现频率比真正崩溃更高,因为jvm本身的设计非常健壮,除非是被kill -9或者操作系统层面内存不足触发了OOM Killer,否则很少直接消失。
jvm频繁full gc怎么排查这通常是假死的元凶
如果进程还在,但应用像睡着了一样,大方向先查垃圾回收,Full GC(全局垃圾回收)时jvm会暂停所有工作线程,如果full gc持续过长或过于频繁,应用就表现为假死。
(注:此小标签系统自动隐藏)
拿到GC日志先看两个数字
排查前先确认jvm启动参数里有没有打印GC日志,没有的话就是个盲盒状态,有条件的话,下次启动务必加上这些参数:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:/data/logs/gc.log
拿到日志后重点看两个指标:
- GC频率:正常情况下,minor gc每秒几次或几秒一次,full gc一天几次或几天一次,如果full gc达到每分钟好几次,肯定是有问题的
- GC停顿时间:单次full gc停顿超过1秒就要警惕,超过10秒基本等于应用不可用
堆内存里到底堆了什么
Full gc频繁的根源,大多是内存里的对象无法被回收,用下面命令抓一个堆内存快照:
jmap -dump:format=b,file=/data/logs/heap.hprof <pid>
然后打开MAT(Eclipse Memory Analyzer)或者VisualVM分析dump文件,优先看:
- 哪个类的实例数量异常巨大
- 哪个对象占据了最多的堆内存
- 对象之间的引用链,看看是谁在“持有”它们
常见的元凶是缓存、静态集合、数据库连接没关闭、线程池队列堆积,举个例子,把数据全部加载到静态HashMap里当缓存用,数据量上来后,堆内存很容易被打满。
linux服务器jvm内存溢出问题定位从操作系统日志逆推
有时候进程直接消失,根本来不及抓dump文件,这时候操作系统日志就是第一破案现场。
dmesg和系统日志是破案关键
执行命令:
dmesg | grep -i oom
如果输出了类似Out of memory: Kill process 12345 (java),说明是操作系统把jvm给杀了,这通常发生在物理机或容器内存被全部耗尽时。
再看一下系统日志:
cat /var/log/messages | grep -i java
如果是云服务器,一般可以在控制台查看系统日志,这一条路径能快速确认两个问题:内存到底有没有耗尽,是谁耗尽了内存。
容器场景下的特殊坑
很多团队把java应用跑在docker容器里,这里有个常见的坑:
jvm默认读取的是宿主机的内存大小,而不是容器的内存限制,老版本jvm可能直接申请超过容器限额的内存,导致被强制杀掉。
排查时看两个值:
- 容器的内存限制:
docker inspect <container_id> | grep Memory - jvm最大堆设置:
jinfo -flag MaxHeapSize <pid>
如果容器限制了2GB,但jvm最大堆竟然设了4GB,那被杀掉只是时间问题。
处理jvm异常退出不能只靠重启
搞清楚了原因,但不能每次都手动重启,线上应用需要具备自动恢复的能力,建议做三件事:
- 配置
-XX:+ExitOnOutOfMemoryError,让应用在OOM时快速退出,然后由supervisor、systemd或k8s自动拉起 - 将jvm启动参数中的GC日志和堆内存配置写入启动脚本,避免人工遗漏
- 设置操作系统层面的监控告警,内存指标达到阈值时提前触发通知,而不是等挂了再处理
systemd守护的配置示例
[Service]
ExecStart=/usr/bin/java -Xms2g -Xmx2g -XX:+ExitOnOutOfMemoryError -jar app.jar
Restart=always
RestartSec=10
这样可以保证jvm异常退出后,10秒内自动重启,减少业务中断时间。
从日志到源码,一步步缩小范围
有些问题从jvm本身找不到根因,比如频繁full gc和线程卡顿是由业务代码引起的,这种情况下还需要继续往下排查。
线程堆积是最容易忽视的异常信号
执行jstack <pid> > thread.txt,然后重点检查:
- 大量线程处于
BLOCKED状态,说明锁竞争严重 - 大量线程处于
WAITING状态,说明线程池配置不适配业务场景 -
自定义线程池的队列长度异常增长
常见的一个案例:线程池核心线程数设置太小,任务队列无限增长,导致线程处理的都是老任务,新任务排队时间越来越长,用户感觉系统挂了,但jvm确实还在运行。
连接池满了也会引发假死现象
数据库连接池满了,线程会一直等待获取连接,表现就是所有请求超时,排查方法:
netstat -an | grep 3306 | wc -l
统计数据库连接数是否超过了连接池上限,如果连接数异常偏高,同时show processlist能看到大量sleep状态的连接,基本可以断定连接池泄露。
这种情况下检查代码里所有获取连接的逻辑,确保finally块里正确释放连接,或者改用try-with-resources语法。
Q&A
进程还在但CPU跑满,是jvm挂了吗?
不一定是jvm挂了,可能是有线程在做密集计算或死循环,用top -H -p <pid>找出CPU占用最高的线程,再用jstack匹配线程id查看对应的业务代码,这种方式定位CPU问题非常高效。
jvm的堆外内存能排查吗?
可以,但相对麻烦,堆外内存(包括直接内存、元空间等)溢出时,jvm本身不会报OOM异常,而是进程被操作系统直接杀掉,排查时结合dmesg和/var/log/messages里的内存信息,同时使用-XX:MaxDirectMemorySize参数限制直接内存大小,排查这类问题一般需要结合glibc的内存分配机制,难度较高。
jvm崩溃日志去哪里找?
jvm发生致命错误时会在启动目录生成类似hs_err_pid.log的文件,里面有崩溃时的线程状态、内存使用情况、原生线程栈等内容,这个文件对于定位linux服务器上jvm崩溃类问题非常关键。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/768552.html




