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 高¶
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 和业务流量分析。
线程阻塞、死锁与连接池耗尽¶
关注:
- 是否出现明确的 Java-level deadlock。
- 大量线程是否阻塞在同一锁、同一 HTTP 调用或数据库连接获取。
- Tomcat/Jetty/Netty 工作线程是否全部忙碌。
- HikariCP 等连接池是否 active 到上限、pending 持续增加。
- 超时时间是否层层倒挂,例如上游 30 秒、应用 60 秒、数据库无限等待。
连接数高时同时检查操作系统:
启动失败¶
| 日志特征 | 常见原因 |
|---|---|
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 |
| 只有单个实例异常 | 实例配置、流量倾斜、节点资源或局部依赖 |
| 所有实例同时异常 | 公共依赖、发布配置、数据库、网络或流量变化 |