Java故障排查
Java 应用从运维视角排查启动、CPU、内存、GC、线程、连接、日志和 Kubernetes 故障。
Java 故障排查
本文档面向运维和值班场景,适用于 Spring Boot、Tomcat、Dubbo 等 Java 服务。原则:先保留现场,再定位根因;先摘流量或扩容,再考虑重启。
排查总流程
确认影响范围
-> 看服务状态和日志
-> 看系统资源
-> 按症状采集 JVM 现场
-> 检查下游依赖
-> 临时止血
-> 复盘和长期修复
先记录这些信息:
- 故障开始时间、影响接口、影响实例。
- 最近是否发布、变更配置、扩容、切流或迁移。
- 是单实例异常、单节点异常,还是整组服务异常。
- 是否有 OOM、重启、探针失败、下游超时、连接池耗尽。
单机排查步骤
1. 服务是否存活
jps -lv
ps -ef | grep java
ss -lntp | grep <port>
如果进程不存在,先看启动日志和系统日志:
tail -n 200 logs/app.log
journalctl -u <service> -n 200 --no-pager
dmesg -T | tail -n 100
2. 日志是否有明确异常
grep -iE 'exception|error|caused by|timeout|refused|oom|deadlock' logs/app.log | tail -n 100
tail -f logs/app.log
重点关注最后一个 Caused by,常见关键词:
| 关键词 | 方向 |
|---|---|
OutOfMemoryError | 堆、Metaspace、直接内存 |
RejectedExecutionException | 线程池满或队列满 |
SocketTimeoutException | 下游慢或网络异常 |
Connection refused | 下游未监听、Service 异常 |
Too many open files | 文件句柄耗尽 |
Communications link failure | 数据库连接异常 |
3. 系统资源是否异常
top
free -h
df -h
df -i
iostat -x 1
ss -s
判断方向:
- CPU 高:进入 CPU 排查。
- 内存高或 OOM:进入内存排查。
- 磁盘满:先清理日志或扩容,再查日志暴涨原因。
- IO 高:关注日志写入、GC、数据库慢盘。
- 连接异常:检查端口、DNS、下游连接数。
JVM 现场采集
建议先创建采集目录:
export PID=<pid>
export TS=$(date +%F-%H%M%S)
mkdir -p /tmp/java-${PID}-${TS}
cd /tmp/java-${PID}-${TS}
通用采集:
ps -fp ${PID} > process.txt
cat /proc/${PID}/limits > limits.txt
cat /proc/${PID}/status > status.txt
jcmd ${PID} VM.command_line > vm-command-line.txt
jcmd ${PID} VM.flags > vm-flags.txt
jcmd ${PID} GC.heap_info > gc-heap-info.txt
jstat -gcutil ${PID} 1000 10 > jstat-gcutil.txt
连续采集线程栈:
for i in 1 2 3; do
jstack -l ${PID} > jstack-${i}.txt
sleep 5
done
需要堆转储时:
jcmd ${PID} GC.heap_dump /tmp/heap-${PID}-${TS}.hprof
堆转储前确认磁盘空间,文件通常很大。
典型故障
CPU 高
top -H -p ${PID}
printf '%x\n' <tid>
jstack -l ${PID} > jstack.txt
grep -n "nid=0x<hex_tid>" jstack.txt
判断:
- 线程在业务代码:排查循环、正则、序列化、加解密、热点逻辑。
- 线程在 GC:转入 GC 排查。
- 多线程都在下游调用:排查数据库、Redis、HTTP 依赖。
内存和 OOM
jstat -gcutil ${PID} 1000 10
jcmd ${PID} GC.heap_info
cat /proc/${PID}/status | egrep 'VmRSS|VmSize|Threads'
dmesg -T | grep -i 'killed process'
判断:
- Old 区长期高位并频繁 Full GC:堆不足或泄漏。
MetaspaceOOM:类加载过多或动态生成类异常。Direct buffer memory:直接内存或 Netty 类组件异常。- 系统 OOM Kill:进程总内存超过系统或容器限制。
GC 频繁
jstat -gcutil ${PID} 1000
jcmd ${PID} VM.flags | tr ' ' '\n' | grep -E 'GC|Heap|RAM|Metaspace'
jcmd ${PID} VM.command_line | tr ' ' '\n' | grep -E 'Xlog|Xloggc|PrintGC'
关注:
FGC是否持续增加。- GC 暂停是否和接口超时重合。
- 容器内存限制是否和
-Xmx、MaxRAMPercentage匹配。
线程阻塞或死锁
jstack -l ${PID} > jstack-lock.txt
grep -nE 'BLOCKED|WAITING|TIMED_WAITING|Found one Java-level deadlock' jstack-lock.txt
大量线程卡在数据库、Redis、HTTP 客户端或日志写入时,继续检查下游连接、网络和连接池配置。
连接池或下游异常
ss -antp | grep ${PID} | awk '{print $1}' | sort | uniq -c
ss -antp | grep ':3306' | wc -l
ss -antp | grep ':6379' | wc -l
curl -v http://<downstream-host>:<port>/<health-path>
数据库侧:
SHOW PROCESSLIST;
SHOW ENGINE INNODB STATUS\G
判断:
CLOSE-WAIT多:应用未正确关闭连接。SYN-SENT多:网络或下游端口不可达。- 连接池日志出现
HikariPool、Druid超时:看连接泄漏、慢 SQL、下游容量。
Kubernetes 集群内排查
1. 找到异常范围
export NS=<namespace>
export APP=<app-label>
kubectl get deploy,sts,pod,svc,endpoints -n ${NS} -o wide
kubectl get pod -n ${NS} -l app=${APP} -o wide
看是否集中在同一版本、同一 Node、同一可用区,关注 READY、RESTARTS、AGE。
2. 看 Pod 状态和事件
export POD=<pod-name>
kubectl describe pod ${POD} -n ${NS}
kubectl get event -n ${NS} --sort-by=.lastTimestamp | tail -n 50
重点看:
Last State是否为OOMKilled。- 是否有
Readiness probe failed或Liveness probe failed。 - 是否发生镜像拉取、挂载、调度失败或驱逐。
requests、limits是否过小。
3. 看日志
kubectl logs ${POD} -n ${NS} --tail=300
kubectl logs ${POD} -n ${NS} --previous --tail=300
kubectl logs ${POD} -n ${NS} -c <container> --tail=300
--previous 对排查崩溃重启非常关键。
4. 进入容器采集 JVM
kubectl exec -it ${POD} -n ${NS} -- sh
pgrep -laf java
export PID=<pid>
jstack -l ${PID} > /tmp/jstack.txt
jcmd ${PID} VM.flags > /tmp/vm-flags.txt
jstat -gcutil ${PID} 1000 10 > /tmp/jstat-gcutil.txt
拷贝文件:
kubectl cp ${NS}/${POD}:/tmp/jstack.txt ./jstack.txt
kubectl cp ${NS}/${POD}:/tmp/vm-flags.txt ./vm-flags.txt
如果镜像没有 JDK 工具,使用调试容器或运维工具镜像辅助排查。
5. 查资源、探针和网络
kubectl top pod ${POD} -n ${NS}
kubectl top node
kubectl get pod ${POD} -n ${NS} -o jsonpath='{.spec.containers[*].resources}'
kubectl get pod ${POD} -n ${NS} -o yaml | grep -A20 -E 'startupProbe|livenessProbe|readinessProbe'
kubectl get svc,endpoints -n ${NS} -o wide
容器内看 cgroup:
cat /sys/fs/cgroup/memory.max 2>/dev/null
cat /sys/fs/cgroup/memory.current 2>/dev/null
cat /sys/fs/cgroup/cpu.max 2>/dev/null
临时网络探测:
kubectl run netshoot-${TS} -n ${NS} --rm -it --image=nicolaka/netshoot -- sh
curl -v http://<service>.<namespace>.svc.cluster.local:<port>/<path>
dig <service>.<namespace>.svc.cluster.local
nc -vz <service> <port>
应急处理
按影响从小到大处理:
- 摘除异常实例流量。
- 扩容同版本实例。
- 限流或降级高风险入口。
- 回滚最近发布。
- 采集现场后重启单实例。
- 临时调大资源限制或连接池限制。
Kubernetes 常用操作:
kubectl scale deploy <deploy> -n ${NS} --replicas=<n>
kubectl rollout restart deploy <deploy> -n ${NS}
kubectl rollout status deploy <deploy> -n ${NS}
kubectl rollout history deploy <deploy> -n ${NS}
kubectl rollout undo deploy <deploy> -n ${NS}
重启或回滚前至少保留:
- 最近应用日志和
--previous日志。 - 线程栈 3 份。
- JVM 参数和 GC 状态。
- Pod 事件和 describe 输出。
- 必要时 Heap Dump。
命令速查
| 场景 | 命令 |
|---|---|
| Java 进程 | jps -lv、`ps -ef |
| 端口监听 | `ss -lntp |
| 线程 CPU | top -H -p <pid> |
| 线程栈 | jstack -l <pid> |
| GC 观察 | jstat -gcutil <pid> 1000 |
| JVM 参数 | jcmd <pid> VM.flags |
| 堆信息 | jcmd <pid> GC.heap_info |
| 堆转储 | jcmd <pid> GC.heap_dump /tmp/heap.hprof |
| 文件句柄 | `ls /proc/<pid>/fd |
| Pod 状态 | kubectl describe pod <pod> -n <ns> |
| Pod 日志 | kubectl logs <pod> -n <ns> --previous |
案例:K8s 中 Java 服务反复重启
现象:
- 订单服务 10 分钟内多次重启。
- 用户偶发 502,监控显示 Pod
RESTARTS持续增加。 - 应用日志只有启动日志,没有明显业务异常。
排查:
export NS=prod
export POD=order-api-6f8c9f9c8b-abcde
kubectl describe pod ${POD} -n ${NS}
kubectl logs ${POD} -n ${NS} --previous --tail=200
kubectl top pod ${POD} -n ${NS}
关键信息:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Limits:
memory: 1024Mi
继续确认 JVM 参数:
kubectl exec -it ${POD} -n ${NS} -- sh
pgrep -laf java
jcmd <pid> VM.flags | tr ' ' '\n' | grep -E 'Xmx|MaxRAM|Metaspace'
发现应用配置 -Xmx1024m,容器内存 limit 也是 1024Mi,没有给 Metaspace、线程栈、直接内存和 JVM Native 内存预留空间。
处置:
kubectl scale deploy order-api -n ${NS} --replicas=6
kubectl set resources deploy order-api -n ${NS} --limits=memory=1536Mi
kubectl rollout status deploy order-api -n ${NS}
后续修复:
- 将
-Xmx调整为容器内存的 60% 到 70%。 - 补充 JVM 内存、Pod OOMKilled、重启次数告警。
- 发布前检查
resources.limits.memory与 JVM 参数是否匹配。 - 保留
--previous日志和 Pod 事件作为复盘证据。
复盘清单
- 根因:代码、配置、容量、下游、网络、发布、数据。
- 影响:接口、实例、用户范围、持续时间。
- 证据:日志、线程栈、GC、监控、K8s 事件。
- 处置:摘流量、扩容、回滚、重启、参数调整。
- 改进:监控告警、容量水位、探针配置、限流降级、发布策略。