JVM 常用排查工具
JVM 排查不是背几个命令,而是把线上现象一步一步还原成证据:进程还在不在、线程卡在哪里、GC 是否异常、对象为什么还活着、内存到底花在堆里还是堆外。
本页按生产排查方式讲清楚 JVM 工具,重点补齐 OOM 排查。零基础先记住一句话:
OOM 不是一句“内存不够”,而是一类结果。先分类,再保留证据,再定位引用链或资源上限,最后用代码、参数和容量设计一起修。
学习目标
学完本页要能回答这些问题:
- 线上 JVM 出问题时先保留哪些证据。
Java heap space、Metaspace、Direct buffer memory、unable to create native thread分别是什么原因。- 为什么不能一看到 OOM 就直接调大
-Xmx。 - heap dump、线程栈、GC 日志、NMT 分别解决什么问题。
- 医疗采集、订单导入、MQ 消费、Netty 网关等商业场景中如何排查 OOM。
为什么要先用工具看证据
线上问题不能靠猜。CPU 高可能是死循环,也可能是频繁 GC;接口慢可能是锁等待,也可能是数据库慢;内存涨可能是缓存,也可能是线程堆积;容器重启可能是 Java 自己抛了 OOM,也可能是 Kubernetes 发现进程超过 cgroup 限制后直接杀掉。
如果不采集线程栈、GC 指标、堆信息和容器事件,直接改代码或调参数,很可能修错方向。
如果你要按面试场景题回答“线上 OOM 怎么定位”,并且需要分别给出 Java 原生命令和 Arthas 两套解决思路,优先看:线上 OOM 怎么定位:Java 原生命令与 Arthas 两套思路。
常见错误做法:
| 错误做法 | 为什么危险 |
|---|---|
一看到 OOM 就把 -Xmx 调大 | 可能只是延缓爆炸,真正的泄漏还在;堆越大,Full GC 和 dump 成本越高 |
| 没保留 dump 就重启 | 重启后现场消失,无法知道哪些对象占内存 |
| 只看平均内存 | OOM 常常发生在峰值、批处理、导入、消费堆积时,平均值掩盖问题 |
| 只看 Java 堆 | 容器内存还包括直接内存、线程栈、元空间、代码缓存、本地库等 |
| 只抓一次线程栈 | 单次快照可能刚好处在正常等待,连续抓多次才有趋势 |
JVM排查总流程
flowchart TD
A["线上服务异常"] --> B["确认现象: 慢、卡、CPU高、内存涨、重启"]
B --> C["保留证据: 日志、GC日志、线程栈、dump、容器事件"]
C --> D{"是否 OOM 或内存异常"}
D -- "是" --> E["按错误文本分类 OOM"]
D -- "否" --> F["按 CPU、线程、锁、IO、下游继续排查"]
E --> G["定位对象、类、直接内存、线程或容器限制"]
G --> H["修代码、限容量、调参数、加监控"]
H --> I["压测验证并复盘预防"]这张图的关键是“先分类”。不同 OOM 的根因完全不同,堆 OOM 查对象引用链,线程 OOM 查线程数量和 -Xss,直接内存 OOM 查 NIO/Netty/ByteBuffer,Metaspace OOM 查类加载器和动态生成类。
证据清单
线上出现 JVM 问题时,优先保留这些证据。
| 证据 | 解决什么问题 | 常用方式 |
|---|---|---|
| 应用错误日志 | 看异常类型、业务链路、traceId | app.log、日志平台 |
| GC 日志 | 看 GC 频率、停顿、回收效果 | -Xloggc、JDK 9+ -Xlog:gc* |
| JVM 参数 | 看堆、元空间、GC、直接内存、容器识别 | jcmd pid VM.flags |
| 线程栈 | 看线程卡在 CPU、锁、IO、连接池还是等待队列 | jstack、jcmd Thread.print |
| 对象直方图 | 看哪些类对象数量和内存大 | jmap -histo:live |
| heap dump | 看对象引用链和为什么不能回收 | jcmd GC.heap_dump、MAT |
| Native Memory | 看堆外、线程栈、类元数据、本地库 | NMT、jcmd VM.native_memory |
| 容器事件 | 判断是否 OOMKilled 或资源限制 | kubectl describe pod |
证据采集顺序要结合风险:heap dump 可能很大,生产上可能造成暂停,先确认磁盘空间和业务影响。线程栈相对轻量,可以连续抓多次。
常用命令速查
| 命令 | 作用 | 适合场景 |
|---|---|---|
jps -l | 查看 Java 进程 | 找 PID |
jcmd pid VM.flags | 查看 JVM 参数 | 确认堆、GC、元空间配置 |
jcmd pid VM.system_properties | 查看系统属性 | 确认环境、编码、配置来源 |
jstat -gcutil pid 1000 | 每秒查看一次 GC 概况 | 判断 Young GC、Full GC 是否频繁 |
jmap -histo:live pid | 查看存活对象直方图 | 快速判断大对象类型 |
jcmd pid GC.class_histogram | 查看类实例数量 | JDK 自带对象统计 |
jcmd pid GC.heap_dump /tmp/app.hprof | 导出堆 dump | 分析引用链和泄漏 |
jstack pid | 导出线程栈 | 卡死、CPU 高、锁等待 |
jcmd pid Thread.print | 打印线程栈 | jstack 替代方式 |
jcmd pid VM.native_memory summary | 查看本地内存 | 直接内存、线程栈、元空间排查 |
Linux 和容器中常配合:
top -Hp 进程ID
pmap -x 进程ID
ulimit -u
kubectl describe pod pod名
kubectl logs pod名 --previouskubectl logs --previous 很重要:如果容器已经重启,当前日志可能看不到上一次崩溃前的 OOM 或错误栈。
OOM类型总览
OOM 必须先看错误文本。文本不同,排查方向不同。
| 错误或现象 | 含义 | 优先排查 |
|---|---|---|
OutOfMemoryError: Java heap space | Java 堆放不下对象 | heap dump、对象直方图、GC Roots 引用链 |
OutOfMemoryError: GC overhead limit exceeded | GC 花大量时间却回收很少 | 堆中大量对象仍存活,查泄漏或容量不足 |
OutOfMemoryError: Metaspace | 类元数据所在本地内存不足 | 动态代理、类加载器泄漏、热部署 |
OutOfMemoryError: Direct buffer memory | 直接内存不足 | NIO、Netty、ByteBuffer.allocateDirect |
OutOfMemoryError: unable to create native thread | 创建线程失败 | 线程数、-Xss、OS 限制、容器限制 |
Requested array size exceeds VM limit | 数组太大超过 JVM 限制 | 大文件、一次性加载、分页/分块设计 |
容器 OOMKilled、退出码 137 | 进程被 cgroup 杀掉 | JVM 总内存和容器 limit,不一定有 Java OOM 栈 |
注意:容器 OOMKilled 不一定会留下 Java OutOfMemoryError。Kubernetes 看到进程超过内存限制后可能直接发信号杀掉进程,Java 还没来得及打印堆栈。
OOM排查完整流程
flowchart TD
A["发现 OOM 或容器重启"] --> B["读取错误文本和容器事件"]
B --> C{"是哪类内存"}
C -- "Java heap space" --> D["导出 heap dump 和对象直方图"]
C -- "Metaspace" --> E["查看类数量、ClassLoader、动态生成类"]
C -- "Direct buffer memory" --> F["查看直接内存、Netty、ByteBuffer 使用"]
C -- "unable to create native thread" --> G["查看线程数、Xss、系统 ulimit"]
C -- "OOMKilled" --> H["核算进程总内存和容器 limit"]
D --> I["用 MAT 看 Dominator Tree 和 GC Roots"]
E --> J["限制动态类、修 ClassLoader 泄漏、设置 MaxMetaspaceSize"]
F --> K["释放 DirectByteBuffer、检查池化和 Netty 泄漏"]
G --> L["收敛线程池、降低 Xss、修无限创建线程"]
H --> M["调整 limit/request 与 JVM 内存预算"]
I --> N["修复后压测和加监控"]
J --> N
K --> N
L --> N
M --> N不要只停在“哪个类占内存最大”。真正的关键是对象为什么还活着。MAT 的 Dominator Tree 解决“谁支配了最多内存”,Path To GC Roots 解决“它为什么没有被回收”。
Heap OOM:Java heap space
堆 OOM 表示 Java 堆里对象太多或对象太大,GC 后仍然放不下新对象。
典型根因:
| 根因 | 例子 | 正确处理 |
|---|---|---|
| 无界集合 | static List 不断 add 采集数据 | 设置容量、分批处理、处理完清理 |
| 缓存无淘汰 | 本地 Map 缓存所有用户权限 | 使用 Caffeine/Redis,设置最大容量和过期 |
| 大文件一次性读入 | Files.readAllBytes 读 2GB 文件 | 流式读取、分块解析、分批入库 |
| MQ 消费内存堆积 | 拉取很多消息放内存再慢慢处理 | 控制拉取批次、消费并发、背压 |
| ThreadLocal 未清理 | 线程池线程保存大对象 | finally remove() |
| 查询无分页 | 一次查出百万行 | 分页、游标、按时间窗口处理 |
Heap OOM排查步骤
- 看 OOM 时间点是否对应导入、批处理、MQ 堆积、活动流量峰值。
- 看 GC 日志,确认 Full GC 后堆是否仍然很高。
- 用
jmap -histo:live pid快速看存活对象类型。 - 导出 heap dump,用 MAT 看 Dominator Tree。
- 对最大对象看 Path To GC Roots,确认引用链来自静态变量、线程、队列、缓存还是 ThreadLocal。
- 回到代码修复容量边界、分页、清理、缓存淘汰或引用释放。
Heap OOM Demo
下面 Demo 用来理解堆 OOM,不要在生产执行。
import java.util.ArrayList;
import java.util.List;
public class HeapOomDemo {
static class Record {
private final byte[] payload = new byte[1024 * 1024];
}
public static void main(String[] args) {
List<Record> records = new ArrayList<>();
while (true) {
records.add(new Record());
System.out.println("records = " + records.size());
}
}
}运行示例:
java -Xms64m -Xmx64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp HeapOomDemo为什么会 OOM:records 一直引用每个 Record,这些对象从线程栈局部变量 records 可以到达,所以 GC 不能回收。不是 GC 不努力,而是对象仍然“活着”。
GC overhead limit exceeded
这个错误表示 JVM 大部分时间都在 GC,但每次只能回收很少内存。它通常不是新类型的根因,而是堆压力的另一种表现。
你可以这样理解:
flowchart TD
A["堆快满"] --> B["频繁 Full GC"]
B --> C{"每次能回收多少"}
C -- "很少" --> D["对象仍被引用"]
D --> E["GC overhead limit exceeded"]常见原因仍然是集合、缓存、队列、ThreadLocal、查询无分页、大对象等。排查方式和 heap OOM 类似,重点看存活对象和引用链。
Metaspace OOM
JDK 8 以后,HotSpot 移除了永久代,类元数据主要放在 Metaspace。Metaspace 使用本地内存,不在 Java 堆里。
JDK 7 和 JDK 8 的高频区别:
| 版本 | 类元数据区域 | 常见错误 |
|---|---|---|
| JDK 7 | 永久代 PermGen | OutOfMemoryError: PermGen space |
| JDK 8 | 元空间 Metaspace,本地内存 | OutOfMemoryError: Metaspace |
Metaspace 里放的是类元数据,不是普通业务对象。普通 new User() 多了通常是堆 OOM;动态生成类、类加载器不释放才更容易导致 Metaspace OOM。
典型根因:
- 动态代理、CGLIB、ByteBuddy、Groovy 脚本持续生成新类。
- 热部署或插件系统反复创建 ClassLoader,但旧 ClassLoader 被线程、缓存、静态变量引用。
- 应用容器中重复部署,旧应用类加载器没有释放。
- 没有限制
-XX:MaxMetaspaceSize,容器内本地内存被元空间吃掉。
排查思路:
jcmd 进程ID VM.classloader_stats
jcmd 进程ID GC.class_histogram
jcmd 进程ID VM.native_memory summaryVM.classloader_stats 能帮助观察 ClassLoader 数量和类元数据占用。NMT 需要启动参数开启,例如:
-XX:NativeMemoryTracking=summaryDirect buffer memory
直接内存是堆外内存,常见于 NIO、Netty、文件传输、网络通信。
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024);它和普通堆对象的区别:
| 类型 | 内存位置 | 常见用途 | 回收特点 |
|---|---|---|---|
ByteBuffer.allocate | Java 堆 | 普通字节数组 | 受堆 GC 管理 |
ByteBuffer.allocateDirect | 堆外本地内存 | NIO、网络、零拷贝场景 | 依赖 DirectByteBuffer 对象和 Cleaner 触发释放 |
直接内存 OOM 常见原因:
- 不断创建 direct buffer,释放不及时。
- Netty
ByteBuf没有 release,引用计数泄漏。 -XX:MaxDirectMemorySize设置过小。- 容器 limit 太小,堆、直接内存、线程栈加起来超过限制。
Direct OOM Demo
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class DirectMemoryOomDemo {
public static void main(String[] args) {
List<ByteBuffer> buffers = new ArrayList<>();
while (true) {
buffers.add(ByteBuffer.allocateDirect(10 * 1024 * 1024));
System.out.println("allocated direct buffers = " + buffers.size());
}
}
}运行示例:
java -Xmx128m -XX:MaxDirectMemorySize=64m DirectMemoryOomDemo为什么 -Xmx 不大也可能 OOM:直接内存不属于 Java 堆,-Xmx 限制的是堆,直接内存还要单独看 MaxDirectMemorySize 和容器总内存。
Netty 项目排查要开启泄漏检测时,可以临时使用:
-Dio.netty.leakDetection.level=advanced生产不要长期最高级别开启,否则性能开销较大。
unable to create native thread
这个错误表示 JVM 想创建新线程,但操作系统或容器拒绝了。
常见原因:
| 原因 | 解释 |
|---|---|
| 线程数太多 | 无限 new Thread,或线程池最大线程数设置过大 |
-Xss 太大 | 每个线程栈占用更多本地内存,能创建的线程数减少 |
| OS 用户线程数限制 | ulimit -u 限制 |
| 容器内存不足 | 线程栈也是进程内存的一部分 |
| 线程泄漏 | 任务结束后线程没有退出,比如自建调度线程、连接线程 |
排查命令:
jstack 进程ID > /tmp/app.jstack
ps -eLf | grep java | wc -l
ulimit -u
jcmd 进程ID VM.flags生产修复方式:
- 禁止接口请求里无限
new Thread。 - 统一使用有界线程池。
- 合理设置线程池最大线程数和队列。
- 对线程命名,方便从线程栈识别来源。
- 必要时调小
-Xss,但要防止递归或深调用导致栈溢出。
容器 OOMKilled 和 Java OOM 的区别
在 Docker/Kubernetes 中,服务经常不是 Java 自己抛 OOM,而是被容器运行时杀掉。
flowchart TD
A["Java进程总内存上涨"] --> B{"是否超过容器 limit"}
B -- "否" --> C["继续运行或 Java 内部报 OOM"]
B -- "是" --> D["内核触发 OOM Killer"]
D --> E["容器退出码 137"]
E --> F["Kubernetes 显示 OOMKilled"]Java 进程总内存不等于 -Xmx。
进程总内存 ~= Java堆 + 元空间 + 直接内存 + 线程栈 + Code Cache + GC本地结构 + JNI/本地库 + JVM自身开销举例:容器 limit 是 1GB,如果你设置 -Xmx900m,再加上 Metaspace、Direct Memory、线程栈和 JVM 自身,很容易超过 1GB 被杀。
生产建议:
- 容器内不要把
-Xmx贴着 limit 设置。 - 预留堆外、线程栈、元空间和 JVM 本身开销。
- JDK 8 旧版本要注意容器内存识别能力,企业老项目尤其要确认。
- 查看
kubectl describe pod的 Last State 和 Events。 - 查看
kubectl logs --previous保留上一次崩溃日志。
CPU飙高排查
CPU 高不一定是业务代码死循环,也可能是 Full GC 太频繁。先分辨 CPU 花在哪。
flowchart TD
A["CPU 飙高"] --> B["top 找进程"]
B --> C["top -Hp 找线程"]
C --> D["线程ID转十六进制"]
D --> E["jstack 匹配 nid"]
E --> F{"线程在做什么"}
F -- "业务代码循环" --> G["检查死循环和算法复杂度"]
F -- "GC线程活跃" --> H["检查 GC 日志和堆对象"]
F -- "锁竞争" --> I["检查 BLOCKED 和锁持有者"]
F -- "Native/IO" --> J["结合系统和下游指标"]命令示例:
jps -l
top -Hp 进程ID
printf "%x\n" 线程ID
jstack 进程ID > /tmp/app.jstack
grep -n "十六进制线程ID" /tmp/app.jstack连续抓三次:
jstack 进程ID > /tmp/app-1.jstack
sleep 2
jstack 进程ID > /tmp/app-2.jstack
sleep 2
jstack 进程ID > /tmp/app-3.jstack如果同一个线程一直停在同一段业务代码,就要结合日志和代码检查死循环、锁等待或慢调用。如果看到 GC 线程持续活跃,要回到 GC 日志和堆对象分析。
线程卡死和接口慢排查
接口慢常见不是 CPU 算得慢,而是线程在等。
线程栈常见状态:
| 状态或栈信息 | 可能含义 |
|---|---|
BLOCKED | 等 synchronized 锁 |
WAITING | 等条件、队列、Future、锁 |
TIMED_WAITING | sleep、超时等待、连接池等待 |
socketRead0 | 等下游网络响应 |
getConnection | 等数据库连接池 |
FutureTask.get、CompletableFuture.join | 等异步任务结果,可能线程饥饿死锁 |
Unsafe.park | AQS 锁、线程池队列、条件等待 |
排查时不要只说“线程 WAITING”。要继续看它在等谁、为什么等、有没有超时、上游是否持续提交新任务。
商业场景:医疗采集导入 Heap OOM
场景:医疗数据采集平台每天导入病案、检验、检查、费用明细。开发为了方便,把 Excel 或 CSV 全部读进内存,再把每行转换成对象放入 List,最后统一批量入库。
错误代码:
List<MedicalRecord> all = new ArrayList<>();
for (String line : Files.readAllLines(path)) {
all.add(parse(line));
}
medicalRecordMapper.batchInsert(all);问题:
Files.readAllLines会把所有文本读到内存。all又保存所有解析后的对象。- 如果文件很大,字符串和对象会同时占用堆。
- 即使触发 GC,因为
all还在引用对象,GC 也不能回收。
正确思路:
List<MedicalRecord> batch = new ArrayList<>(1000);
try (BufferedReader reader = Files.newBufferedReader(path)) {
String line;
while ((line = reader.readLine()) != null) {
batch.add(parse(line));
if (batch.size() == 1000) {
medicalRecordMapper.batchInsert(batch);
batch.clear();
}
}
}
if (!batch.isEmpty()) {
medicalRecordMapper.batchInsert(batch);
batch.clear();
}为什么这样更稳:内存中最多保留一批数据,不会因为文件总行数变大而线性撑爆堆。批大小要结合单条记录大小、数据库能力、事务时长和失败重试成本压测确定。
商业场景:MQ消费者堆积导致 OOM
场景:订单支付成功后消息进入 MQ,消费者把消息先拉到内存队列,再由另一个线程慢慢写数据库。活动高峰时数据库慢,内存队列不断上涨,最后 OOM。
根因链路:
flowchart TD
A["生产者发送速度高"] --> B["消费者预拉取大量消息"]
B --> C["内存队列缓存消息"]
C --> D["数据库写入变慢"]
D --> E["队列只进不出"]
E --> F["堆内存上涨"]
F --> G["Java heap space"]修复方向:
- 控制单次拉取数量和本地队列容量。
- 消费速度要受数据库连接池和写入能力约束。
- 失败重试要退避,避免重试风暴。
- 消费逻辑做幂等,允许小批量提交。
- 监控消费 lag、队列长度、处理耗时、失败率。
商业场景:Netty网关 Direct Memory OOM
场景:采集网关用 Netty 接收医院前置机长连接。某个 Handler 保存了 ByteBuf 但没有释放,或者异步处理时引用计数没有正确 retain/release,运行一段时间后报 Direct buffer memory。
排查方向:
- 看错误是否是
Direct buffer memory。 - 查看 Netty leak detector 日志。
- 检查自定义 Handler 中是否手动保存、转发、异步处理了
ByteBuf。 - 检查是否重复 retain 但少 release。
- 看 direct memory 配置和容器 limit。
修复原则:
| 场景 | 做法 |
|---|---|
当前 Handler 消费完 ByteBuf | 确保 release |
| 继续传递给下一个 Handler | 不要提前 release |
| 异步线程继续使用 | 先 retain,异步结束后 release |
| 不熟悉引用计数 | 优先使用 Netty 提供的安全模式和框架约定 |
商业场景:ThreadLocal泄漏
场景:接口过滤器把当前用户、租户、traceId 放进 ThreadLocal,业务中还放了较大的上下文对象。线程池线程处理完请求后没有清理,下次请求复用同一个线程。
危害:
- value 挂在线程的 ThreadLocalMap 上,线程不退出就可能长期不释放。
- 下一个请求可能读到上一个请求的用户或租户。
- heap dump 里可能看到对象被
Thread -> ThreadLocalMap -> Entry -> value引用。
标准写法:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
UserContextHolder.set(parseUser(request));
chain.doFilter(request, response);
} finally {
UserContextHolder.remove();
}
}排查结果如何落地修复
JVM 排查的终点不是“发现哪个类最多”,而是把问题变成工程改动。
| 根因 | 修复方向 |
|---|---|
| 无界集合 | 增加容量上限、分批处理、及时 clear |
| 本地缓存过大 | 设置最大容量、过期策略、监控命中率和大小 |
| 大文件一次性加载 | 流式读取、分页、游标、分片任务 |
| MQ 消费内存堆积 | 限制拉取、本地队列有界、背压、幂等、降级 |
| ThreadLocal 泄漏 | finally remove(),禁止保存大对象 |
| 线程过多 | 收敛线程池、命名、监控、限流 |
| 直接内存泄漏 | 修 release、设置直接内存上限、检查 Netty 引用计数 |
| Metaspace 增长 | 查动态类和 ClassLoader 泄漏,限制生成类 |
| 容器 OOMKilled | 重新核算堆、堆外、线程栈和 limit |
常见面试题
OOM怎么完整排查
先看错误文本分类,不能一上来就调大堆。堆 OOM 用 GC 日志、对象直方图、heap dump 和 MAT 看 Dominator Tree、Path To GC Roots;Metaspace OOM 查动态生成类和 ClassLoader 泄漏;Direct buffer memory 查 NIO、Netty、直接内存上限和 ByteBuf 释放;unable to create native thread 查线程数、线程池、-Xss、OS 限制;容器 OOMKilled 要看 cgroup limit、退出码 137 和 kubectl describe pod。
为什么不能只调大Xmx
如果是内存泄漏,调大 -Xmx 只是让泄漏更晚爆发;如果是容器 OOM,堆调大还可能让进程总内存更容易超过 limit;如果是 Direct Memory、Metaspace、线程栈,-Xmx 甚至不是核心限制。正确做法是先分类,再定位对象或资源来源。
heap dump主要看什么
主要看 Dominator Tree、对象数量、Retained Heap 和 Path To GC Roots。Dominator Tree 告诉你谁支配了最多内存,Path To GC Roots 告诉你对象为什么还活着。只有知道引用链,才能判断是缓存、静态集合、线程、队列还是 ThreadLocal 导致对象不能回收。
容器OOMKilled和Java OOM区别
Java OOM 是 JVM 内部抛出 OutOfMemoryError,通常有错误栈和可能的 heap dump;OOMKilled 是操作系统或容器运行时发现进程超过 cgroup 内存限制后直接杀掉进程,可能没有 Java OOM 栈。排查 OOMKilled 要看 Pod 事件、退出码 137、上一次日志和进程总内存预算。
TLAB属于堆吗
属于。TLAB 是 Eden 区里给线程预留的一小块线程本地分配缓冲区,目的是让线程分配小对象时减少竞争。对象分配在 TLAB 里仍然是堆对象,仍然受 GC 管理。它和逃逸分析的分配消除不是一回事,详见 JIT逃逸分析。
本章小结
JVM 排查要从证据出发。CPU 高看线程和 GC,接口慢看线程栈和下游等待,OOM 先按错误文本分类,再用 dump、直方图、NMT、容器事件定位根因。真正的生产能力不是会背 jmap,而是能把工具结果翻译成容量边界、代码修复、参数调整和监控告警。
