1. 如何判断对象是否存活?

四种引用(由强到弱):

🎯 面试要点

  • 循环引用对象会被可达性分析正确回收(JVM 不采用引用计数)
  • 软引用 OOM 前被回收:-XX:SoftRefLRUPolicyMSPerMB 控制存活时长
  • 延伸:ThreadLocal 内存泄漏 = 弱引用 key 被回收 + value 强引用链不断 → 用完必须 remove()

2. 分代收集理论?三大回收算法?

分代假说:绝大多数对象朝生夕死(弱分代);熬过多次 GC 的对象越难死(强分代)。据此把堆分为新生代(Eden + 2 个 Survivor,8:1:1)和老年代。

🎯 面试要点

  • 新生代回收(Minor GC)用复制算法:Eden 满触发;存活对象从 Eden+Survivor 复制到另一个 Survivor,年龄 +1,够 15 进老年代
  • Survivor 放不下直接进老年代(分配担保)
  • CMS 用标记清除 → 碎片 → 触发 Full GC 时退化为标记整理(Serial Old)

3. CMS 收集器的工作过程?优缺点?

CMS(Concurrent Mark Sweep):以"最短停顿"为目标的老年代收集器,四大步骤:

  1. 初始标记(STW):只标记 GC Roots 直接引用的对象,很快
  2. 并发标记:与用户线程并发,从初始标记对象追踪全部存活对象(时间长但不暂停)
  3. 重新标记(STW):修正并发期间用户线程产生的变动(增量更新)
  4. 并发清除:与用户线程并发清理死亡对象

缺点:

🎯 面试要点

  • CMS 三色标记问题:并发标记时引用变化 → 用增量更新(CMS)或原始快照 SATB(G1)解决
  • JDK9 标记弃用,JDK14 移除,被 G1 替代
  • CMS 的两个 STW 都很短,适合低延迟应用(旧场景)

4. G1 收集器的设计?为什么 JDK9 起成为默认?

Region 化整为零:堆被划分为大小相等的 Region(默认 2048 个,1~32M),逻辑上仍分代(Region 标记为 Eden/Survivor/Old/Humongous 大对象区)。回收以 Region 为单位,不再全堆扫描。

核心机制:

🎯 面试要点

  • G1 目标:在不牺牲太多吞吐的前提下,把停顿控制在可预测范围内(200ms 级别)
  • 大对象(超过 Region 一半)放 Humongous Region
  • G1 的弱点是:分配过猛/并发标记赶不上时,退化为 Full GC(JDK 10 起 Full GC 已并行化,不再是 JDK 8 那种 Serial 式全停顿)
  • 对比 CMS:G1 无碎片、停顿可预测、能回收整个堆(新生代+老年代)

5. ZGC 为什么能做到毫秒级停顿?

ZGC 的停顿不随堆大小增长(JDK15 起可线上使用,JDK17 增强),适合大堆低延迟场景;G1 的停顿会随堆增长而变长。

🎯 面试要点

  • ZGC 只支持部分平台(Linux x86_64/aarch64、Windows 等)
  • 染色指针的代价:寻址范围受限(JDK 15 上限 4TB;JDK 21 起支持 16TB 堆)
  • Shenandoah 类似思路(不用染色指针,用转发指针)——可提一句显深度

6. GC 调优参数与调优思路?

常用 JVM 参数
-Xms2g -Xmx2g                    # 堆大小,生产建议 -Xms = -Xmx 避免动态伸缩
-Xmn512m                         # 新生代大小
-Xss256k                         # 线程栈大小(调小可支撑更多线程)
-XX:MetaspaceSize=256m           # 元空间
-XX:+UseG1GC                     # JDK9+ 默认,无需显式
-XX:MaxGCPauseMillis=200         # G1 停顿目标
-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/   # OOM 自动 dump,必配!
# JDK 9+ 统一日志(旧参数 PrintGCDetails/-Xloggc 在 JDK 14 已彻底移除)
-Xlog:gc*,gc+heap=info:file=/data/logs/gc.log:time,uptime,level,tags
-XX:+DisableExplicitGC           # 禁用 System.gc() 防止频繁 Full GC

调优流程:

  1. 明确目标:低延迟(缩短 STW)还是高吞吐(减少 GC 总耗时)?二者矛盾
  2. 观察现状:GC 日志看频率/时长,jstat 看各区占用
  3. 调整方向:Minor GC 频繁 → 增大新生代;Full GC 频繁 → 老年代不足/大对象过多/内存泄漏;对象过早晋升 → 检查大对象和 Survivor 容量
  4. 预防性策略:避免大对象、避免频繁创建对象(复用)、合理设置线程池和连接池、排除泄漏

🎯 面试要点

  • 调优不是调参数,先排代码问题(泄漏/大对象),参数只是辅助
  • Full GC 频繁先查:堆太小?大对象直接进老年代?System.gc 被调?类加载器泄漏(Metaspace)?
  • GC 日志分析重点:GC (Allocation Failure)、Full GC、耗时异常长、GC 后空间没降下来

🎤 常见面试追问

  1. 可达性分析里 GC Roots 有哪些?——栈帧中的局部变量引用、静态变量引用、常量引用、JNI(本地方法)引用、被 synchronized 持有的对象等。
  2. 强/软/弱/虚引用分别什么时候用?——强:默认;软:内存不足才回收(图片缓存);弱:下一次 GC 必回收(ThreadLocal 的 key、WeakHashMap);虚:只用于跟踪回收(DirectByteBuffer 的 Cleaner)。
  3. G1 和 CMS 的核心区别?——CMS 是"标记清除 + 碎片化 + 停顿不可控";G1 把堆分成 Region、按回收价值优先回收、停顿可预测、无碎片(复制整理)。
  4. 为什么 Full GC 频繁?——① 老年代不够(对象过早晋升/大对象多);② 堆设太小;③ 代码调 System.gc();④ 类加载器泄漏撑爆元空间。先查 jstat 再对症。
  5. Minor GC 和 Full GC 的触发时机?——Minor:Eden 满;Full:老年代满 / 元空间满 / System.gc() / G1 并发回收赶不上分配。

📖 名词解释(本页术语)

术语 大白话解释
可达性分析判断对象死活的方法:从 GC Roots 出发能走到的对象活着,走不到的就是垃圾。替代有缺陷的"引用计数法"。
标记-清除 / 标记-复制 / 标记-整理三大回收算法:清除(有碎片)、复制(无碎片但费空间,新生代用)、整理(无碎片,老年代用)。
分代收集按对象年龄分区域管理:新生代频繁小回收(复制算法),老年代少回收(标记整理)——大部分对象朝生夕死的规律。
Minor GC / Full GCMinor = 只回收新生代(快、频繁);Full = 回收整个堆(慢、停顿长,要尽量避免)。
Stop The World(STW)GC 时暂停所有业务线程(保证对象图稳定),停顿越长体验越差——调优就是缩短 STW。
CMS老年代收集器:并发标记清除,停顿短但碎片多、吞吐略降。JDK9 起废弃。
G1JDK9+ 默认收集器:堆分 Region,优先回收价值最高的区域,停顿可预测(MaxGCPauseMillis)。
ZGC毫秒级停顿收集器(染色指针 + 读屏障),停顿不随堆大小增长,适合大堆低延迟。
软/弱/虚引用三种"弱化"的引用:软引用(内存不足才回收)、弱引用(下次 GC 必回收)、虚引用(只用于回收通知)。
⚠️ 本页面由 AI 生成,内容仅供参考,请以官方文档和实际源码为准。