← 大纲 第七部分 · JVM 与性能优化 · 第 71~75 题

第七部分:JVM 与性能优化(第 71~75 题)

进入「第三阶段架构师能力」。聚焦 Full GC、内存泄漏、CPU 100%、OOM、GC 调优。每题需给出:现象→诊断工具→根因→参数/代码修复→预防。

本页目录: #71 FullGC频繁#72 内存泄漏#73 CPU100% #74 OOM全景#75 GC调优

第 71 题:Full GC 频繁与 STW 治理 GC

1.【真实企业业务场景】
交易服务 Full GC 每 2 分钟一次,每次 STW 800ms,接口 P99 从 50ms 飙到 1s,监控显示老年代涨满即回收。要求:定位 Full GC 根因并消除。
2.【面试官问题】
    1. Full GC 触发原因?2. 怎么诊断?3. 老年代涨满?4. 大对象/内存碎片?5. 参数怎么调?
3.【候选人的标准回答】

第一步:分析。Full GC=全局回收,STW 长。常见原因:老年代满、元空间满、显式 System.gc、晋升失败、大对象直入老年代。

第二步:挑战。定位、晋升、碎片、参数。

第三步:架构。诊断:GC 日志(-Xlog:gc*)+ jstat + dump 分析;②根因:老年代涨满→存在长期对象/缓存无界;③修复:限缓存大小(弱引用/LRU)、大对象分批、调 Survivor/晋升阈值;④选型:G1/ZGC 降 STW。

第四步:选型。G1(默认,Region)/ ZGC(亚毫秒 STW,大堆)。

第五步:一致性。GC 调优不影响语义;降 STW 保延迟。

第六步:高可用。避免 Full GC 致雪崩;HPA 按延迟。

第七步:优化。对象生命周期管理;避免 System.gc。

4.【架构设计】
GC日志/jstat → 老年代涨满 → dump分析(长期对象/无界缓存) 修复: 限缓存 + 大对象分批 + 调晋升 + 换G1/ZGC
5.【技术方案深度解析】

触发:老年代满触发 Full GC( serial old 或 G1 的 mixed/full)。无界缓存:最常见——本地 Map 一直 put 不淘汰,老年代涨满。大对象:超过 Region 50% 直入老年代,碎成 Full GC。G1/ZGC:G1 增量回收 Region,ZGC 并发几乎无 STW,适合低延迟大堆。

6.【关键技术点】
FullGCSTWG1ZGC晋升失败GC日志
7.【Java 实现示例】
// 参数: G1 + GC日志
-Xms4g -Xmx4g -XX:+UseG1GC
-Xlog:gc*:time,level,tags:file=gc.log:time
// 修复无界缓存 → Caffeine LRU
Cache<K,V> c = Caffeine.newBuilder().maximumSize(10000).build();
// 避免: System.gc(); 大对象分批处理
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 只加内存——掩盖问题。正确:定位根因。
  • ❌ 无界缓存——老年代满。正确:LRU/限大小。
  • ❌ 用 System.gc——强制 Full GC。正确:避免。
10.【架构师评分标准】
初级 0~40
不知 Full GC 触发。
中级 40~60
知加内存,不论证。
高级 60~80
诊断+根因(缓存/大对象)+G1/ZGC。
架构师 80~100
再加:①GC 日志体系;②晋升/碎片原理;③大堆 ZGC;④容量与延迟权衡。

第 72 题:内存泄漏定位与修复 泄漏

1.【真实企业业务场景】
服务运行 3 天内存缓慢涨到上限,OOM 重启,Heap 里 ThreadLocal 持有大对象未清理,或静态 Map 缓存无限增长。要求:定位并根治内存泄漏。
2.【面试官问题】
    1. 泄漏 vs 正常增长?2. 怎么 dump 分析?3. ThreadLocal 泄漏?4. 静态集合?5. 预防?
3.【候选人的标准回答】

第一步:分析。泄漏=对象不再用但被引用无法回收,堆持续涨。常见:ThreadLocal 未 remove、静态集合、未关资源(连接/流)、监听器。

第二步:挑战。定位、根因、修复、预防。

第三步:架构。监控:内存曲线 + Full GC 频率;②dumpjmap -dump 或 OOM 自动 dump;③分析:MAT 看支配树(谁持有大对象);④修复:ThreadLocal 用 try-finally remove、缓存加上限/弱引用、资源 try-with-resources;⑤预防:OOM 自动 dump + 压测长稳。

第四步:选型。MAT / VisualVM + JVM 参数(HeapDumpOnOutOfMemoryError)。

第五步:一致性。泄漏修复不影响语义;降内存保稳定。

第六步:高可用。OOM 自动 dump 不重启丢现场;HPA 兜底。

第七步:优化。对象池复用;监控长稳。

4.【架构设计】
内存曲线异常 → jmap dump → MAT支配树(谁持有大对象) 修复: ThreadLocal.remove / 缓存上限 / try-with-resources 预防: OOM自动dump + 长稳压测
5.【技术方案深度解析】

ThreadLocal 泄漏:线程池线程复用,ThreadLocal 不被 GC(线程存活),必须 finally 中 remove,否则每次任务累积大对象。静态集合:static Map 永不回收,需 LRU/弱引用/定时清理。资源未关:b>连接/流/会话持有堆外或堆内对象,用 try-with-resources 保关闭。MAT 支配树:找「被谁引用而活」,直接定位根因。

6.【关键技术点】
内存泄漏ThreadLocalMAT支配树HeapDump弱引用
7.【Java 实现示例】
// ThreadLocal 必须 remove
private static final ThreadLocal<BigObj> TL = new ThreadLocal<>();
try { TL.set(load()); doWork(); }
finally { TL.remove(); } // 防线程池累积泄漏
// JVM 参数: OOM 自动 dump
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/heap.hprof
// 静态缓存: 用 WeakHashMap 或 Caffeine 上限
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ ThreadLocal 不 remove——线程池泄漏。正确:finally remove。
  • ❌ 静态 Map 无限——泄漏。正确:上限/弱引用。
  • ❌ 资源不关——泄漏。正确:try-with-resources。
10.【架构师评分标准】
初级 0~40
不会 dump。
中级 40~60
知 MAT,不论 ThreadLocal。
高级 60~80
dump+MAT+ThreadLocal/静态集合+修复。
架构师 80~100
再加:①堆外泄漏(NMT);②长稳压测;③OOM 自动 dump;④预防体系。

第 73 题:CPU 100% 与死循环/锁竞争 CPU

1.【真实企业业务场景】
某服务 CPU 打满 100%,RT 暴涨,但 QPS 没涨。排查发现一段正则灾难性回溯 + 一个自旋锁。要求:定位 CPU 热点并修复。
2.【面试官问题】
    1. 怎么定位高 CPU 线程?2. 正则回溯?3. 锁竞争/自旋?4. 频繁 GC 也吃 CPU?5. 预防?
3.【候选人的标准回答】

第一步:分析。CPU 100% 常因:死循环、正则回溯、锁竞争/自旋、频繁 GC、序列化过载。

第二步:挑战。定位线程、根因、修复。

第三步:架构。定位top -Hp pid 找高 CPU 线程 → jstack 看栈;②正则:用线性复杂度正则/预编译,避免 (a+)+$ 回溯;③:自旋/ CAS 改公平锁/减小临界区;④GC:频繁 Full GC 占 CPU,先治 GC;⑤预防:压测 + CPU 监控。

第四步:选型。top/jstack/arthas + 火焰图(async-profiler)。

第五步:一致性。修复不影响语义;降 CPU 保吞吐。

第六步:高可用。限流/降级防 CPU 打满雪崩。

第七步:优化。热点代码火焰图定位;算法优化。

4.【架构设计】
top -Hp → 高CPU线程 → jstack/arthas 看栈(死循环/锁) 正则回溯: 改线性正则; 锁竞争: 减临界区/公平锁 火焰图定位热点
5.【技术方案深度解析】

定位套路:top 找进程→top -Hp 找线程→换算 16 进制→jstack 匹配 nid。正则回溯:嵌套量词 (a+)+b 对长串匹配失败会指数回溯,改用 possessive/原子组或线性写法。自旋锁:while(CAS) 空转吃满 CPU,改 Lock(park)或限自旋次数。火焰图:async-profiler 采样,直观看 CPU 花在哪。

6。【关键技术点】
top -Hpjstackarthas正则回溯火焰图自旋锁
7.【Java 实现示例】
// 正则防回溯: 用 possessive (++) 或预编译+线性
Pattern p = Pattern.compile("a++b"); // 贪婪占有, 不回溯
// 自旋改 Lock
private final Lock lock = new ReentrantLock();
lock.lock(); try { critical(); } finally { lock.unlock(); }
// 诊断: top -Hp <pid> ; jstack <pid> | grep -A 20 <nid>
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 正则嵌套量词——回溯爆 CPU。正确:线性/possessive。
  • ❌ 自旋空转——吃满 CPU。正确:Lock/park。
  • ❌ 不查 GC——漏因。正确:jstat 看 GC 时间。
10.【架构师评分标准】
初级 0~40
不会定位。
中级 40~60
知 jstack,不论正则/锁。
高级 60~80
top-Hp+jstack+正则/锁+火焰图。
架构师 80~100
再加:①arthas 实战;②频繁 GC 关联;③序列化热点;④预防与压测。

第 74 题:OOM 全景与各类溢出 OOM

1.【真实企业业务场景】
线上出现多种 OOM:Java heap spaceMetaspaceDirect bufferUnable to create new native thread。要求:区分类型并分别给出治理方案。
2.【面试官问题】
    1. 各类 OOM 区别?2. 堆外 OOM?3. 线程数 OOM?4. 元空间 OOM?5. 预防?
3.【候选人的标准回答】

第一步:分析。OOM 不是一种:堆、元空间、直接内存、线程栈各有根因,必须分而治之。

第二步:挑战。类型区分、根因、修复。

第三步:架构。heap:内存泄漏/堆太小→dump+MAT(Q72);②Metaspace:类加载泄漏(CGLIB/反射)→限制 + 查卸载;③Direct buffer:Netty 直接内存未释放/超上限→调 -XX:MaxDirectMemorySize + 池化;④unable create thread:线程数超限(线程池失控/栈过大)→限线程池 + 降栈;⑤预防:自动 dump + 监控。

第四步:选型。JVM 参数 + NMT(Native 跟踪)+ MAT。

第五步:一致性。修复不影响语义;降内存保稳定。

第六步:高可用。OOM 自动 dump 留现场;HPA 兜底。

第七步:优化。对象池/线程池上限;监控。

4.【架构设计】
OOM类型 → 根因 → 修复 heap: 泄漏 → MAT; metaspace: 类泄漏 → 限大小 direct: Netty未释放 → 池化+限; thread: 池失控 → 限线程
5.【技术方案深度解析】

堆 OOM:最常见,泄漏或堆设小。元空间:动态类(CGLIB 代理、Groovy、反射生成)不断生成不卸载,调 MaxMetaspaceSize + 查类加载器泄漏。Direct buffer:Netty 默认用堆外,未 release() 或超 MaxDirectMemorySize→OOM;用池化 ByteBuf + 显式 release。线程 OOM:每线程占栈(默认 1M),线程池无上限→线程爆→无法建线程;限池大小 + 降 -Xss。

6.【关键技术点】
heap OOMMetaspaceDirectBuffer线程OOMNMT类加载泄漏
7.【Java 实现示例】
// 关键参数
-Xmx4g -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1g -Xss256k
-XX:+HeapDumpOnOutOfMemoryError
// Netty 直接内存: 池化 + release
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(n);
try { ... } finally { buf.release(); }
// 线程池必须上限
new ThreadPoolExecutor(..., new LinkedBlockingQueue<>(1000), reject);
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 所有 OOM 只加堆——其他区无效。正确:分区治。
  • ❌ Netty 不 release——堆外泄漏。正确:池化+release。
  • ❌ 线程池无上限——线程 OOM。正确:限大小。
10.【架构师评分标准】
初级 0~40
只知 heap OOM。
中级 40~60
知堆外,不论分类型。
高级 60~80
四类OOM区分+根因+参数+修复。
架构师 80~100
再加:①NMT 跟踪;②类加载泄漏;③池化与上限;④自动 dump 与监控体系。

第 75 题:GC 选型与参数调优 GC调优

1.【真实企业业务场景】
交易服务(堆 8G)用 Parallel GC,吞吐高但 STW 偶发 1s 致超时;另一服务(堆 32G)要亚毫秒延迟。要求:给出 GC 选型与调优策略。
2.【面试官问题】
    1. 各 GC 适用?2. 吞吐 vs 延迟?3. 参数怎么调?4. 大堆选谁?5. 调优方法论?
3.【候选人的标准回答】

第一步:分析。GC 选型看目标:吞吐(Parallel)还是延迟(G1/ZGC)。无银弹。

第二步:挑战。目标权衡、参数、大堆。

第三步:架构。Parallel:高吞吐、可接受长 STW(批处理);②G1:平衡(默认,<=数十 G,可控停顿);③ZGC/Shenandoah:大堆亚毫秒 STW(低延迟服务);④调优:设堆大小(Xms=Xmx 防抖动)、Region、并发线程;⑤方法:先测基线→定位瓶颈→调参→验证。

第四步:选型。批处理 Parallel;普通服务 G1;低延迟大堆 ZGC。

第五步:一致性。GC 不影响语义;调优降 STW。

第六步:高可用。低延迟 GC 防超时雪崩。

第七步:优化。对象生命周期短(Young 多)= GC 友好;避免大对象。

4.【架构设计】
目标: 吞吐 → Parallel; 平衡 → G1; 低延迟大堆 → ZGC 调优: Xms=Xmx + Region + 并发线程 + 实测验证
5.【技术方案深度解析】

Parallel:多线程吞吐最高,但 Full GC STW 长,适合离线批处理。G1:Region 化增量回收,停顿目标可设(-XX:MaxGCPauseMillis),默认首选。ZGC:并发标记/转移,STW <10ms 且不随堆增长,适合 8G~数 T 低延迟。Xms=Xmx:避免堆动态扩缩抖动;调优先测:别盲调,靠 GC 日志 + 压测数据。

6.【关键技术点】
Parallel GCG1ZGC吞吐vs延迟MaxGCPause调优方法论
7.【Java 实现示例】
// 交易服务(平衡): G1
-Xms8g -Xmx8g -XX:+UseG1GC -XX:MaxGCPauseMillis=200
// 低延迟大堆: ZGC
-Xms32g -Xmx32g -XX:+UseZGC -XX:ZCollectionInterval=...
// 批处理: Parallel
-XX:+UseParallelGC
// 验证: -Xlog:gc*:file=gc.log + 压测对比 P99
8.【面试官可能继续追问】
9.【常见错误回答】
  • ❌ 一律 Parallel——延迟差。正确:按目标选。
  • ❌ Xms≠Xmx——抖动。正确:相等。
  • ❌ 盲调参数——无依据。正确:实测驱动。
10.【架构师评分标准】
初级 0~40
不知 GC 类型。
中级 40~60
知 G1,不论场景。
高级 60~80
三类GC适用+参数+调优法。
架构师 80~100
再加:①吞吐/延迟量化权衡;②大堆 ZGC;③容器内存约束;④压测验证闭环。