Java 垃圾回收
Java 垃圾回收(Garbage Collection, GC)机制是 Java 虚拟机(JVM)的核心功能之一,负责自动管理内存。以下是对其详细的解释:
一、Java 内存区域划分
Java 堆内存主要分为三个区域:
-  新生代(Young Generation) - 新创建的对象优先分配在此区域。
- 分为三个部分: - Eden 区:对象初始分配的区域。
- Survivor 区(S0 和 S1):Eden 区满时,存活的对象被移至 Survivor 区。两个 Survivor 区始终有一个为空,用于复制算法。
 
 
-  老年代(Old Generation) - 存放长期存活的对象(如缓存对象、静态变量引用的对象)。
- 当对象在 Survivor 区经历多次 GC 后仍存活(默认 15 次),会被晋升到老年代。
 
-  永久代 / 元空间(PermGen/Metaspace) - 永久代(Java 7 及以前):存储类信息、常量池等。
- 元空间(Java 8 及以后):使用本地内存,避免永久代的内存溢出问题。
 
二、垃圾回收的核心概念
-  可达性分析(Reachability Analysis) - 从GC Roots(如静态变量、栈帧中的引用、本地方法栈中的引用等)出发,遍历对象引用链。未被引用的对象被标记为不可达,可被回收。
 
-  引用类型 - 强引用:Object obj = new Object(),只要强引用存在,对象就不会被回收。
- 软引用(SoftReference):内存不足时会被回收,常用于缓存。
- 弱引用(WeakReference):下次 GC 时立即回收,如WeakHashMap。
- 虚引用(PhantomReference):仅在对象被回收时收到通知,用于管理堆外内存。
 
- 强引用:
-  垃圾回收算法 - 标记 - 清除(Mark-Sweep):标记可达对象,清除未标记对象,会产生内存碎片。
- 标记 - 整理(Mark-Compact):标记后将存活对象移动到一端,避免碎片。
- 复制(Copying):将内存分为两块,每次只使用一块,GC 时将存活对象复制到另一块,效率高但空间利用率低。
- 分代收集(Generational Collection):结合上述算法,新生代用复制算法,老年代用标记 - 清除或标记 - 整理。
 
三、对象从新生代到老年代的晋升过程
-  对象创建 - 新对象首先分配在 Eden 区。若 Eden 区空间不足,触发Minor GC(新生代 GC)。
 
-  第一次 Minor GC - 存活的对象被移至Survivor 区(S0 或 S1),并将对象年龄(GC 次数)设为 1。
- Eden 区被清空。
 
-  后续 Minor GC - 每次 Minor GC 后,存活的对象在两个 Survivor 区之间来回复制,年龄递增。
- 当对象年龄达到阈值(默认 15 次),晋升到老年代。
 
-  动态对象年龄判定 - 若 Survivor 区中相同年龄的对象大小总和超过 Survivor 区的一半,年龄大于或等于该年龄的对象直接晋升到老年代。
 
-  大对象直接进入老年代 - 超过-XX:PretenureSizeThreshold参数设置的大对象(如长数组),直接分配到老年代,避免在 Eden 区和 Survivor 区之间复制。
 
- 超过
-  空间分配担保 - 老年代需确保有足够空间容纳新生代晋升的对象。若老年代空间不足,触发Full GC(整堆 GC)。
 
四、垃圾回收器类型
不同的垃圾回收器适用于不同场景,JVM 会根据配置和硬件自动选择:
-  新生代回收器 - Serial:单线程,适合客户端应用。
- ParNew:Serial 的多线程版本,与 CMS 配合使用。
- Parallel Scavenge:多线程,注重吞吐量(CPU 用于运行代码的时间比例)。
 
-  老年代回收器 - Serial Old:单线程,用于 Client 模式或与 CMS 配合。
- Parallel Old:多线程,与 Parallel Scavenge 配合,注重吞吐量。
- CMS(Concurrent Mark Sweep):以获取最短停顿时间为目标,并发标记和清除,但会产生碎片。
- G1(Garbage-First):分代收集,将堆划分为多个 Region,优先回收垃圾最多的区域。
- ZGC(Z Garbage Collector):超低延迟,支持 TB 级内存,适用于大内存应用。
 
-  全区域回收器 - Shenandoah:与 G1 类似,但更注重低延迟。
- Epsilon:实验性回收器,仅分配内存,不执行 GC,用于性能测试。
 
五、GC 触发条件
- Minor GC:Eden 区空间不足时触发。
- Full GC: - 老年代空间不足(如晋升对象大小超过老年代剩余空间)。
- 元空间 / 永久代满(如动态加载大量类)。
- 显式调用System.gc()(可能被 JVM 忽略)。
- CMS GC 时并发模式失败(Concurrent Mode Failure)。
 
六、GC 性能调优要点
-  监控工具 - jstat:查看 GC 统计信息。
- jmap:生成堆转储文件。
- jhat/- MAT:分析堆转储文件。
- G1GCViewer/- GCeasy:可视化 GC 日志。
 
-  常用参数 - -Xms/- -Xmx:初始 / 最大堆内存。
- -Xmn:新生代大小。
- -XX:SurvivorRatio:Eden 区与 Survivor 区的比例。
- -XX:MaxTenuringThreshold:晋升老年代的年龄阈值。
- -XX:+UseG1GC:启用 G1 回收器。
 
-  优化方向 - 减少 Full GC 频率,避免大对象和内存泄漏。
- 根据应用特性选择回收器(如 Web 应用优先用 CMS/G1,科学计算优先用 Parallel)。
- 调整新生代大小,平衡 Minor GC 和晋升频率。
 
七、示例:对象晋升流程
假设 JVM 配置为:
- 堆内存:2GB
- 新生代:512MB(Eden: 400MB,S0: 56MB,S1: 56MB)
- 老年代:1.5GB
- 晋升阈值:15 次
流程:
- 新对象(100MB)分配在 Eden 区。
- Eden 区占满 400MB 时,触发 Minor GC。
- 存活的对象(200MB)被复制到 S0,对象年龄设为 1。
- 下次 Minor GC 时,Eden 区和 S0 的存活对象(150MB)被复制到 S1,年龄 + 1。
- 当对象年龄达到 15 次,或 Survivor 区空间不足时,对象晋升到老年代。
面试题
1. 什么是 Java 垃圾回收机制?为什么需要它?
Java GC 是一种自动内存管理机制,由 JVM 负责回收不再使用的对象占用的内存。它的主要作用是:
- 避免手动内存管理导致的内存泄漏和悬空指针问题。
- 提高开发效率,让程序员专注于业务逻辑。
- 通过分代回收和内存整理,优化内存利用率。
2. 如何判断一个对象是否可以被回收?
JVM 使用 ** 可达性分析(Reachability Analysis)** 算法:
- 从 GC Roots(如静态变量、栈帧中的引用、本地方法栈中的引用等)出发,遍历对象引用链。
- 不可达的对象被标记为可回收(如无任何引用指向的对象)。
- 引用类型(强、软、弱、虚)会影响对象的回收优先级。
3. 简述 Java 堆内存的分区及各区域的特点。
Java 堆分为:
- 新生代(Young Generation): - 新对象优先分配在此区域,分为 Eden 区和两个 Survivor 区(S0、S1)。
- 频繁触发 Minor GC,使用复制算法。
 
- 老年代(Old Generation): - 存放长期存活的对象(如经历多次 Minor GC 后仍存活的对象)。
- 触发 Full GC 的频率较低,使用标记 - 清除或标记 - 整理算法。
 
- 元空间 / 永久代(Metaspace/PermGen): - 存储类信息、常量池等,Java 8 后使用元空间(本地内存)替代永久代。
 
4. 对象从新生代到老年代的晋升过程是怎样的?
- 对象创建:新对象分配在 Eden 区。
- Minor GC:Eden 区满时触发,存活对象移至 Survivor 区(如 S0),年龄设为 1。
- 后续 GC:每次 GC 后存活对象在 Survivor 区之间复制,年龄递增。
- 晋升条件: - 对象年龄达到阈值(默认 15 次)。
- 动态年龄判定:相同年龄对象总和超过 Survivor 区一半,大于等于该年龄的对象直接晋升。
- 大对象(超过PretenureSizeThreshold)直接进入老年代。
 
5. 什么是 Minor GC、Major GC 和 Full GC?
- Minor GC(新生代 GC):清理新生代,触发频繁,速度快。
- Major GC(老年代 GC):清理老年代,通常伴随至少一次 Minor GC。
- Full GC(整堆 GC):清理整个堆(新生代、老年代、元空间),触发条件包括: - 老年代空间不足。
- 元空间满。
- 显式调用System.gc()。
- CMS GC 时并发模式失败。
 
6. 常见的垃圾回收器有哪些?简述其特点。
- 新生代回收器: - Serial:单线程,适合客户端应用。
- ParNew:Serial 的多线程版本,与 CMS 配合。
- Parallel Scavenge:多线程,注重吞吐量。
 
- 老年代回收器: - Serial Old:单线程,用于 Client 模式或与 CMS 配合。
- Parallel Old:多线程,与 Parallel Scavenge 配合。
- CMS:以低停顿为目标,并发标记和清除,会产生碎片。
- G1:分代收集,将堆划分为 Region,优先回收垃圾最多的区域。
 
- 全区域回收器: - ZGC:超低延迟,支持 TB 级内存。
 
7. CMS 和 G1 垃圾回收器的区别是什么?
| 特性 | CMS | G1 | 
|---|---|---|
| 回收区域 | 仅针对老年代 | 全堆(分 Region) | 
| 算法 | 标记 - 清除(会产生碎片) | 标记 - 整理(减少碎片) | 
| 停顿时间 | 低停顿,并发标记和清除 | 可预测的停顿,优先回收价值高的 Region | 
| 适用场景 | 响应时间敏感的 Web 应用 | 大内存、多 CPU 的服务器应用 | 
| 内存布局 | 连续的老年代空间 | 划分为多个大小相等的 Region | 
8. 什么是内存泄漏?如何排查?
 内存泄漏:对象不再使用,但 GC 无法回收其占用的内存(如静态集合持有对象引用、未关闭的资源等)。
 排查方法:
 
- 工具:使用jstat、jmap生成堆转储文件,通过MAT或jhat分析。
- 步骤: - 监控内存使用趋势(持续增长可能存在泄漏)。
- 对比不同时间点的堆转储文件,找出增长异常的类。
- 检查对象引用链,确定泄漏原因(如静态集合、单例持有对象)。
 
9. 如何优化 Java GC 性能?
- 选择合适的回收器: - Web 应用:CMS/G1(低延迟)。
- 科学计算:Parallel(高吞吐量)。
 
- 调整堆大小: - -Xms/- -Xmx设置相同值,避免动态扩展。
- 合理分配新生代和老年代比例(如新生代占 1/3 堆内存)。
 
- 减少 Full GC: - 避免大对象直接进入老年代。
- 控制对象晋升阈值,减少老年代压力。
 
- 监控与分析: - 启用 GC 日志,使用工具(如 GCeasy)分析停顿时间和频率。
 
10. 什么是 OOM(OutOfMemoryError)?常见原因有哪些?
 OOM:JVM 无法分配新内存且 GC 无法回收足够空间时抛出的错误。
 常见原因:
 
- 堆内存不足(-Xmx设置过小或内存泄漏)。
- 元空间 / 永久代满(动态加载大量类,如框架反射)。
- 线程创建过多(每个线程占用栈空间)。
- 直接内存溢出(如 NIO 使用ByteBuffer.allocateDirect())。
11. 简述 GC Roots 的类型。
GC Roots 是可达性分析的起始点,包括:
- 虚拟机栈(栈帧中的本地变量表):方法执行时创建的局部变量。
- 方法区中的类静态属性引用的对象:如static Object obj。
- 方法区中的常量引用的对象:如字符串常量池中的引用。
- 本地方法栈中 JNI(Native 方法)引用的对象。
12. 如何理解 “Stop The World”?
 Stop The World(STW):GC 执行时,JVM 暂停所有用户线程的现象。
 
- 原因:为确保对象引用关系在分析期间不变,需暂停用户线程。
- 影响:高并发场景下可能导致系统响应卡顿。
- 优化:使用并发回收器(如 CMS、G1、ZGC)减少 STW 时间。
13. 简述 G1 回收器的工作流程。
 G1 将堆划分为多个大小相等的 Region,工作流程:
 
- 初始标记(Initial Mark):STW,标记 GC Roots 直接关联的对象。
- 并发标记(Concurrent Marking):与用户线程并发执行,遍历对象图。
- 最终标记(Final Mark):STW,修正并发标记期间的变动。
- 筛选回收(Live Data Counting and Evacuation): - 计算各 Region 的回收价值,优先回收垃圾最多的 Region。
- 使用复制算法,将存活对象移至新 Region,减少碎片。
 
14. -XX:+UseConcMarkSweepGC和-XX:+UseG1GC的区别是什么?
 
- -XX:+UseConcMarkSweepGC:启用 CMS 回收器,适用于老年代,以低停顿为目标,采用并发标记 - 清除算法,但会产生内存碎片,可能触发 Full GC。
- -XX:+UseG1GC:启用 G1 回收器,全堆回收,将堆划分为 Region,优先回收价值高的区域,可预测停顿时间,适合大内存应用。
15. 如何监控 Java GC?
- 命令行工具: - jstat -gc <pid> <interval>:查看 GC 统计信息。
- jmap -heap <pid>:查看堆内存使用情况。
- jcmd <pid> GC.heap_dump:生成堆转储文件。
 
- 可视化工具: - VisualVM、Java Mission Control(JMC)、GCeasy(分析 GC 日志)。
 
- GC 日志: - 添加 JVM 参数-XX:+PrintGCDetails -XX:+PrintGCTimeStamps记录 GC 信息。
 
- 添加 JVM 参数
总结
理解 GC 机制的核心原理(分代收集、可达性分析、回收算法)、常见回收器的特性,以及如何通过工具监控和优化 GC 性能,是面试中的高频考点。建议结合实际项目经验,深入理解 GC 调优的思路和方法。
