由浅入深的JVM与垃圾回收面试题及解析

128 阅读5分钟

问题1:简述JVM的垃圾回收机制,核心流程是什么?

回答:
JVM的垃圾回收(GC)是自动管理内存的核心机制。其核心流程分为三步:

  1. 标记(Marking)  :通过可达性分析算法(根搜索算法),从GC Roots(如虚拟机栈引用的对象、静态属性等)出发,标记所有存活对象。
  2. 清除(Sweeping)  :回收未被标记的对象占用的内存空间。不同区域(如新生代、老年代)采用不同算法,例如新生代常用复制算法,老年代用标记-清除标记-整理
  3. 整理(Compacting)  (可选):对内存空间进行整理以减少碎片(如标记-整理算法)。
    GC由JVM的守护线程自动触发,程序无法强制控制,只能通过System.gc()建议执行,但无法保证立即回收。

问题2:如何判断对象是否可以被回收?引用计数和可达性分析的区别是什么?

回答:
判断对象可回收的两种经典方法:

  1. 引用计数法:为每个对象维护引用计数器,当引用为0时回收。但无法解决循环引用问题(如对象A引用B,B引用A)。
  2. 可达性分析法(根搜索算法)  :从GC Roots(如虚拟机栈、静态变量、JNI引用等)出发,遍历对象引用链,未被引用的对象标记为可回收。这是JVM的实际实现方式。
    区别:引用计数法实现简单但存在循环引用缺陷;可达性分析更复杂但能彻底解决循环引用问题。

问题3:JVM的分代垃圾回收机制是如何设计的?为什么需要分代?

回答:
JVM将堆内存划分为新生代(Young Generation)和老年代(Old Generation),并采用分代回收策略:

  1. 新生代:存放生命周期短的对象,分为Eden区和两个Survivor区(S0/S1)。采用复制算法

    • 对象先在Eden分配,Minor GC时存活对象复制到Survivor区,年龄+1。
    • 年龄达到阈值(默认15)后晋升到老年代。
  2. 老年代:存放长期存活对象和大对象,采用标记-清除标记-整理算法。
    分代原因:不同对象生命周期差异大,分代可针对性优化GC效率。新生代GC频繁但速度快,老年代GC次数少但耗时长。


问题4:常见的垃圾收集器有哪些?CMS和G1的区别是什么?

回答:
常见收集器及特点:

  • Serial/ParNew:单线程/多线程新生代收集器,适用于客户端模式。
  • CMS(Concurrent Mark Sweep)  :以最短停顿为目标的老年代收集器,采用标记-清除算法,分四阶段:初始标记(STW)、并发标记、重新标记(STW)、并发清除。但存在内存碎片问题。
  • G1(Garbage-First)  :面向服务端的全堆收集器,将堆划分为多个Region,采用标记-整理算法,可预测停顿时间,兼顾吞吐量和低延迟。
    CMS vs G1
  • CMS仅用于老年代,G1管理整个堆。
  • CMS产生内存碎片,G1通过Region间复制整理减少碎片。
  • G1可设定最大停顿时间(-XX:MaxGCPauseMillis),CMS无此能力。

问题5:如何通过JVM调优减少Full GC的频率?

回答:
优化方向包括:

  1. 合理分配堆内存

    • 增大新生代比例(-Xmn),避免过早晋升到老年代。
    • 避免大对象直接进入老年代(-XX:PretenureSizeThreshold)。
  2. 优化GC策略

    • 老年代使用G1替代CMS,减少碎片和Full GC触发。
    • 启用空间分配担保-XX:+HandlePromotionFailure)避免晋升失败。
  3. 监控与日志分析

    • 通过-Xlog:gc*输出详细GC日志,分析Full GC触发原因(如老年代不足、元空间溢出)。
    • 使用工具(如JVisualVM、Arthas)监控内存泄漏。

案例:某应用频繁Full GC,发现是因年轻代过小导致对象快速晋升。调整-Xmn增大新生代,并改用G1收集器后,Full GC频率下降90%。


问题6:强引用、软引用、弱引用、虚引用的区别及使用场景?

回答:

  • 强引用(Strong Reference):普遍使用的引用(如Object obj = new Object()),只要存在强引用,对象不会被回收。
  • 软引用(Soft Reference):内存不足时回收,适合缓存(如图片缓存)。
  • 弱引用(Weak Reference):无论内存是否充足,GC时立即回收,适用于弱缓存(如WeakHashMap)。
  • 虚引用(Phantom Reference):无法通过虚引用访问对象,主要用于跟踪对象被回收的状态(如DirectByteBuffer的堆外内存释放)。

问题7:什么是Stop-The-World(STW)?如何减少STW时间?

回答:

  • STW:GC过程中暂停所有用户线程,导致应用停顿。所有GC算法均会触发STW,但时间长短不同。

  • 优化方法

    1. 选择低停顿收集器(如G1、ZGC)。
    2. 减少堆大小(需平衡OOM风险)。
    3. 避免Full GC:优化代码减少大对象、调整分代比例。
    4. 启用并发标记(如CMS的并发标记阶段)。

问题8:如何排查OOM(OutOfMemoryError)问题?

回答:
步骤:

  1. 分析错误日志:确定是堆溢出(java.lang.OutOfMemoryError: Java heap space)、元空间溢出(Metaspace)还是栈溢出。

  2. 内存快照

    • 添加-XX:+HeapDumpOnOutOfMemoryError参数生成堆转储文件。
    • 使用MAT(Memory Analyzer Tool)分析对象占用。
  3. 代码检查

    • 检查是否有内存泄漏(如未关闭资源、集合对象未清理)。
    • 确认JVM参数是否合理(如-Xmx设置过小)。

案例:某服务OOM,MAT分析发现是本地缓存使用强引用未清理,改用软引用后问题解决