问题1:简述JVM的垃圾回收机制,核心流程是什么?
回答:
JVM的垃圾回收(GC)是自动管理内存的核心机制。其核心流程分为三步:
- 标记(Marking) :通过可达性分析算法(根搜索算法),从GC Roots(如虚拟机栈引用的对象、静态属性等)出发,标记所有存活对象。
- 清除(Sweeping) :回收未被标记的对象占用的内存空间。不同区域(如新生代、老年代)采用不同算法,例如新生代常用复制算法,老年代用标记-清除或标记-整理。
- 整理(Compacting) (可选):对内存空间进行整理以减少碎片(如标记-整理算法)。
GC由JVM的守护线程自动触发,程序无法强制控制,只能通过System.gc()建议执行,但无法保证立即回收。
问题2:如何判断对象是否可以被回收?引用计数和可达性分析的区别是什么?
回答:
判断对象可回收的两种经典方法:
- 引用计数法:为每个对象维护引用计数器,当引用为0时回收。但无法解决循环引用问题(如对象A引用B,B引用A)。
- 可达性分析法(根搜索算法) :从GC Roots(如虚拟机栈、静态变量、JNI引用等)出发,遍历对象引用链,未被引用的对象标记为可回收。这是JVM的实际实现方式。
区别:引用计数法实现简单但存在循环引用缺陷;可达性分析更复杂但能彻底解决循环引用问题。
问题3:JVM的分代垃圾回收机制是如何设计的?为什么需要分代?
回答:
JVM将堆内存划分为新生代(Young Generation)和老年代(Old Generation),并采用分代回收策略:
-
新生代:存放生命周期短的对象,分为Eden区和两个Survivor区(S0/S1)。采用复制算法:
- 对象先在Eden分配,Minor GC时存活对象复制到Survivor区,年龄+1。
- 年龄达到阈值(默认15)后晋升到老年代。
-
老年代:存放长期存活对象和大对象,采用标记-清除或标记-整理算法。
分代原因:不同对象生命周期差异大,分代可针对性优化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的频率?
回答:
优化方向包括:
-
合理分配堆内存:
- 增大新生代比例(
-Xmn),避免过早晋升到老年代。 - 避免大对象直接进入老年代(
-XX:PretenureSizeThreshold)。
- 增大新生代比例(
-
优化GC策略:
- 老年代使用G1替代CMS,减少碎片和Full GC触发。
- 启用空间分配担保(
-XX:+HandlePromotionFailure)避免晋升失败。
-
监控与日志分析:
- 通过
-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,但时间长短不同。
-
优化方法:
- 选择低停顿收集器(如G1、ZGC)。
- 减少堆大小(需平衡OOM风险)。
- 避免Full GC:优化代码减少大对象、调整分代比例。
- 启用并发标记(如CMS的并发标记阶段)。
问题8:如何排查OOM(OutOfMemoryError)问题?
回答:
步骤:
-
分析错误日志:确定是堆溢出(
java.lang.OutOfMemoryError: Java heap space)、元空间溢出(Metaspace)还是栈溢出。 -
内存快照:
- 添加
-XX:+HeapDumpOnOutOfMemoryError参数生成堆转储文件。 - 使用MAT(Memory Analyzer Tool)分析对象占用。
- 添加
-
代码检查:
- 检查是否有内存泄漏(如未关闭资源、集合对象未清理)。
- 确认JVM参数是否合理(如
-Xmx设置过小)。
案例:某服务OOM,MAT分析发现是本地缓存使用强引用未清理,改用软引用后问题解决