跳转至

JVM 与 Spring 排障

启动参数及资源预算见 JVM 核心参数与系统影响。本页用于根据实际故障收集证据。

排障先区分四层:宿主机/容器资源、JVM、Spring 应用、外部依赖。CPU 高不一定是 GC,内存使用率高不等于泄漏,接口超时也可能是数据库连接池等待。

第一轮现场信息

date
uptime
free -h
df -h
ps -ef | grep '[j]ava'
top -H -p <pid>
ss -s
ss -lntp
jcmd <pid> VM.version
jcmd <pid> VM.command_line
jcmd <pid> VM.flags

同时保存应用版本、容器 ID、启动时间、Profile、变更记录和告警时间。不要先重启再取证;如果必须恢复业务,至少先保存日志、线程栈和资源快照。

CPU 高

容器/进程 CPU 高
  → top -H 找高 CPU 线程
  → 将线程 ID 转为十六进制
  → 在线程栈中查 nid
  → 判断业务循环、锁竞争、GC、序列化或本地调用
top -H -p <pid>
printf '%x\n' <线程ID>
jcmd <pid> Thread.print > /tmp/thread-1.txt
sleep 10
jcmd <pid> Thread.print > /tmp/thread-2.txt

连续取 3 次线程栈比单次更可靠。相同线程长期停在同一业务栈,才更像死循环或热点;大量线程等待同一锁,重点查锁竞争;CPU 与 GC 同步升高则继续检查堆占用和分配速率。

内存高与 OOM

Java 进程内存不只有 Heap:

容器内存
├── Java Heap
├── Metaspace / Compressed Class Space
├── Thread Stack
├── Direct Buffer / NIO
├── Code Cache / JIT
└── JNI、本地库与文件映射

因此容器内存接近上限但 Heap 不高时,应检查线程数、直接内存和 Native Memory。先确认到底是哪一种:

现象 含义
java.lang.OutOfMemoryError: Java heap space Heap 无法再分配,可能容量不足或对象泄漏
GC overhead limit exceeded 大量时间 GC,但回收很少
Metaspace 类元数据增长,可能类加载器泄漏
Direct buffer memory NIO/Netty 直接内存耗尽
容器 OOMKilled 内核杀死进程,应用可能来不及打印 Java OOM
unable to create native thread 线程数、内存或系统进程限制不足
jcmd <pid> GC.heap_info
jcmd <pid> VM.native_memory summary
jcmd <pid> GC.class_histogram
jcmd <pid> GC.heap_dump /data/dumps/heap-$(date +%Y%m%d-%H%M%S).hprof

Heap Dump 会造成磁盘和停顿压力,文件可能包含用户数据、密码和业务对象。执行前确认磁盘空间、权限、业务影响和安全存放位置。VM.native_memory 需要 JVM 启动时启用 Native Memory Tracking 才能得到完整信息。

判断泄漏不能只看“使用率高”:应观察多次 Full GC 后的存活基线是否持续上升,并结合对象直方图、Heap Dump 和业务流量分析。

线程阻塞、死锁与连接池耗尽

jcmd <pid> Thread.print -l > /tmp/threads.txt

关注:

  • 是否出现明确的 Java-level deadlock。
  • 大量线程是否阻塞在同一锁、同一 HTTP 调用或数据库连接获取。
  • Tomcat/Jetty/Netty 工作线程是否全部忙碌。
  • HikariCP 等连接池是否 active 到上限、pending 持续增加。
  • 超时时间是否层层倒挂,例如上游 30 秒、应用 60 秒、数据库无限等待。

连接数高时同时检查操作系统:

ss -ant | awk '{print $1}' | sort | uniq -c
ss -antp | grep <pid>

启动失败

日志特征 常见原因
UnsupportedClassVersionError Java 运行版本低于 JAR 构建版本
Address already in use 应用端口或管理端口被占用
ConfigDataLocationNotFoundException 外部配置路径错误或文件未挂载
BindException / 属性绑定错误 YAML 层级、类型、环境变量格式错误
BeanCreationException 继续追最底层 Caused by,常见为依赖或配置失败
数据源初始化失败 地址、DNS、证书、账号权限、连接数或数据库未就绪

Spring 堆栈很长时不要只看最上面的 Application run failed,应沿 Caused by 找到最底层的网络、权限、配置或 SQL 异常。

GC 日志与 JFR

GC 日志用于判断分配速率、停顿、Full GC 和回收效果;JFR 可以同时观察 CPU、分配、锁、线程、I/O 等事件。

jcmd <pid> JFR.start name=incident settings=profile duration=120s filename=/data/dumps/incident.jfr
jcmd <pid> JFR.check

JFR 和 Dump 都应写入有空间、受权限控制的持久目录,并在采集后关联具体时间、实例、版本和故障现象。

故障归类

证据 更可能的方向
CPU 高,固定业务线程持续 RUNNABLE 热点代码、循环、序列化或加密计算
CPU 高,同时 GC 时间显著增加 Heap 压力、分配过快或泄漏
CPU 不高,线程大量 WAITING 获取连接 数据库/HTTP 连接池耗尽或下游变慢
Heap 平稳但容器内存持续上涨 Direct Memory、线程栈、Native Memory
只有单个实例异常 实例配置、流量倾斜、节点资源或局部依赖
所有实例同时异常 公共依赖、发布配置、数据库、网络或流量变化

官方参考:Oracle Java Troubleshooting Guide