主题
面试题蒸馏卡:应用服务内存与CPU爆满排查
问题:应用服务内存或 CPU 爆满的发生场景?遇到这种问题怎么处理?
一句话机制
服务"爆满"本质是资源供给 < 资源消耗速率:内存侧是对象分配快于 GC 回收(泄漏/大对象)或堆外无上限;CPU 侧是单/多线程长期 100% 执行有效指令(死循环/FGC/锁竞争/密集计算)。排查核心:先定性(内存 or CPU)→ 定位热点(线程/对象)→ 反推代码。
1. 内存爆满(OOM / 堆只涨不降)
发生场景
- 内存泄漏:静态集合只增不删、本地缓存无 TTL/容量上限、
ThreadLocal用完未remove、连接/流未关闭、监听器未注销 - 大对象瞬间吃满:一次性
SELECT *全表、大文件读入byte[]、深分页、超大 JSON 反序列化 - 突发流量:缓存击穿 → DB 压力 → 线程堆积 → 堆涨
- JVM 参数不当:
-Xmx过小;堆外(NIODirectBuffer/Metaspace/线程栈)无上限 → 堆没满但进程被 OS kill - 第三方库 bug:恶意/超大文本 JSON 解析
怎么定位
bash
top / htop # 找高内存 PID
top -Hp <pid> # 看哪个线程最忙
jstat -gcutil <pid> 1s # FGC 频繁 + 老年代接近 100% → 泄漏信号
jmap -histo:live <pid> | head # 对象实例数 Top,定位泄漏类型
jmap -dump:live,format=b,file=heap.hprof <pid> # 导堆
# MAT / jhat 分析支配树、GC Roots 路径
jcmd <pid> VM.native_memory # 堆外(NMT)线上优先 Arthas:dashboard / heapdump / thread / memory,免重启。
怎么处理
- 修泄漏点(缓存加 TTL+容量、
ThreadLocal配对remove、用池化/流式) - 大对象改分页/游标/流式读写
- 调参:合理
-Xmx、-XX:MaxMetaspaceSize、-XX:MaxDirectMemorySize - 限流 + 降级 + 熔断,防流量冲垮内存
2. CPU 爆满
发生场景
- 死循环 / 递归无终止、正则灾难性回溯(
(a+)+$类) - 频繁 FGC(内存泄漏引发,CPU 被 GC 线程吃满)
- 锁竞争/自旋、同步粒度粗、大量线程上下文切换
- 加密/压缩/序列化密集、大对象 JSON 解析
- 外部调用慢 → 线程池打满 → 大量超时重试
怎么定位
bash
top -Hp <pid> # 高 CPU 线程的 tid
printf "%x\n" <tid> # 转 16 进制
jstack <pid> | grep -A 30 <nid> # 看该线程栈,定位代码行
# Arthas: thread -n 3 # 直接看最忙的 3 个线程
# async-profiler / perf # 火焰图定位热点方法怎么处理
- 修死循环/优化正则(加原子组、限制回溯)
- 降锁粒度、用无锁结构、限流防线程堆积
- 异步化、缓存热点、扩容
追问链(面试官会怎么接着问)
- 内存和 CPU 爆满的共同根因是什么? 先排除流量尖峰,再怀疑代码 bug——外部流量 vs 内部泄漏/死循环。
- 怎么快速区分内存泄漏还是突发流量? 老年代"只涨不落"、FGC 后回收不掉 → 泄漏;QPS 曲线同步涨 → 流量。
- Arthas 常用哪几个命令?
dashboard(总览)/thread -n 3(最忙线程)/heapdump(导堆)/watch(方法入参出参)。 - Full GC 频繁但堆没满,可能是什么? 堆外
DirectBuffer、Metaspace满、或被显式System.gc()调用。 - 怎么证明是某集合泄漏而非别的原因? MAT 看支配树(Dominator Tree)+ GC Roots 路径,找到"谁还引用着这个大对象"。
常见误解
- ❌ "CPU 100% 一定是死循环" —— 也可能是频繁 FGC、锁竞争、加密/序列化密集。
- ❌ "OOM 就是
-Xmx太小" —— 堆外内存/Metaspace/线程栈无上限也会 OOM,且堆没满也能被 OS kill。 - ❌ "
top看 RES 高就是泄漏" —— 可能是缓存正常占用,关键看是否持续增长且不随 GC 回落。