JVM 的 GC(Garbage Collection,垃圾收集)

JVM 的 GC(Garbage Collection,垃圾收集) 是 Java 自动内存管理的核心机制。它的主要任务是自动识别并回收不再使用的对象,释放内存空间,防止内存泄漏和溢出


🧠 一、GC 的基本原理

1. 什么是“垃圾”?

在 JVM 中,“垃圾”指的是 已经不再被任何引用链访问到的对象,也就是程序中无法再访问到的“无用对象”。

2. 如何判断对象是否可回收?

JVM 使用 可达性分析算法(Reachability Analysis)

  • 从一组称为 GC Roots 的对象开始,向下搜索引用链。
  • 如果一个对象与 GC Roots 之间 没有引用路径相连,则该对象被认为是“不可达”的,可以被回收。
✅ 常见的 GC Roots 包括:
  • 虚拟机栈(栈帧中的本地变量表)中的引用对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中 JNI(Native 方法)的引用对象
  • 正在被运行的线程对象

🎯 举例:
一个对象 A 被局部变量引用 → 局部变量在栈中 → 栈是 GC Roots → 所以 A 可达,不会被回收。
如果局部变量出作用域,引用消失 → A 不可达 → 可被回收。


🧹 二、GC 是如何清理内存的?(详细过程)

GC 的清理过程分为以下几个阶段,不同垃圾收集器实现略有差异,但基本流程相似。

阶段 1:标记(Mark)

  • 从 GC Roots 出发,遍历所有可达对象,给它们打上“存活”标记。
  • 所有没有被标记的对象,就是“垃圾”。

⚠️ 问题:如果对象很多,标记过程会暂停应用线程(Stop-The-World),影响性能。

阶段 2:清除(Sweep)

根据不同的 GC 算法,有多种清除方式:

🔹 2.1 标记-清除(Mark-Sweep)
  • 直接回收未被标记的对象内存。
  • 缺点
    • 产生内存碎片(不连续的空闲内存)
    • 分配大对象时可能失败,即使总内存足够
🔹 2.2 标记-整理(Mark-Compact)
  • 将所有存活对象向内存一端移动,然后清理边界以外的内存。
  • 优点:消除内存碎片
  • 缺点:移动对象需要时间,暂停时间更长
🔹 2.3 复制算法(Copying)
  • 将内存分为两块(如 Eden + Survivor),每次只使用一块。
  • GC 时,将存活对象复制到另一块,然后清空原区域。
  • 优点:无碎片,效率高
  • 缺点:内存利用率只有 50%

💡 现代 JVM 通常结合多种算法,例如新生代用 复制算法,老年代用 标记-整理


🔄 三、JVM 的分代收集模型(Generational Collection)

JVM 根据对象的生命周期将堆内存分为 新生代(Young)老年代(Old),采用不同的 GC 策略。

1. 新生代(Young Generation)

  • 存放新创建的对象
  • 大多数对象“朝生夕死”,很快变成垃圾
  • 使用 复制算法,GC 频繁但速度快
新生代结构:
Eden + Survivor0 + Survivor1
  • 对象先分配在 Eden
  • Minor GC 后,存活对象复制到 Survivor
  • 经过多次 GC 仍存活的对象,晋升到老年代

📌 通常设置 -XX:MaxTenuringThreshold 控制晋升年龄。

2. 老年代(Old Generation)

  • 存放长期存活的对象
  • GC 频率低,但耗时长
  • 使用 标记-整理 或 标记-清除 算法

🛠️ 四、常见的垃圾收集器(Garbage Collectors)

收集器适用区域算法特点
Serial新生代复制单线程,简单高效,适用于客户端
Parallel(吞吐量优先)新生代/老年代复制 / 标记-整理多线程,关注吞吐量
CMS(Concurrent Mark Sweep)老年代标记-清除并发收集,低停顿,但有碎片
G1(Garbage First)整个堆标记-整理 + 复制面向大内存,可预测停顿时间
ZGC / Shenandoah整个堆读屏障 + 并发整理超低延迟,支持 TB 级堆

🔍 G1 收集器工作流程(以 G1 为例):

G1 将堆划分为多个 Region,优先回收垃圾最多的 Region。

  1. 初始标记(Initial Mark):标记 GC Roots 直接引用的对象(STW)
  2. 并发标记(Concurrent Mark):并发遍历对象图
  3. 最终标记(Remark):处理并发阶段的变动(STW)
  4. 筛选回收(Copy/Compact):选择回收价值最高的 Region 进行清理

✅ G1 的优势:可设置最大停顿时间(-XX:MaxGCPauseMillis


⚙️ 五、GC 的触发时机

GC 类型触发条件
Minor GCEden 区满时触发
Major GC / Full GC老年代空间不足、方法区满、调用 System.gc() 等

❗ Full GC 会导致整个应用暂停(Stop-The-World),应尽量避免频繁发生。


📊 六、GC 的“Stop-The-World”(STW)

  • 在某些 GC 阶段(如标记、整理),JVM 必须暂停所有应用线程,确保对象图一致性。
  • STW 时间越长,应用响应越差。
  • 现代 GC(如 G1、ZGC)通过并发标记、增量整理等方式 减少 STW 时间

🔍 七、如何监控和分析 GC?

1. 开启 GC 日志

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

2. 常用命令

jstat -gc <pid>          # 查看 GC 统计
jmap -heap <pid>         # 查看堆内存使用
jconsole                   # 图形化监控
jvisualvm                  # 强大的分析工具

3. 分析工具

  • GCViewer:分析 GC 日志,查看吞吐量、停顿时间
  • Eclipse MAT:分析堆转储,查找内存泄漏
  • JProfiler:商业级性能分析工具

🚨 常见 GC 问题与调优建议

问题可能原因解决方案
频繁 Minor GCEden 区太小增大 -Xmn
老年代增长快对象过早晋升调整晋升年龄或增大 Survivor
Full GC 频繁老年代空间不足增大 -Xmx,优化对象生命周期
GC 停顿时间长使用 Serial 或 CMS改用 G1 或 ZGC
OutOfMemoryError内存泄漏或配置不足分析堆转储,修复泄漏,调整参数

✅ 总结:GC 是如何工作的?

  1. 判断垃圾:通过可达性分析,找出不可达对象
  2. 标记存活对象:从 GC Roots 开始遍历
  3. 清理内存:根据算法(复制、标记-清除、标记-整理)回收垃圾
  4. 分代收集:新生代用复制算法,老年代用标记-整理
  5. 选择收集器:根据应用需求选择合适的 GC(如 G1 适合大堆低延迟)
  6. 监控调优:通过日志和工具分析 GC 行为,优化参数

💡 一句话总结
GC 就像一个自动清洁工,定期检查哪些对象“没人用了”,然后把它们清理掉,腾出空间给新对象使用。

如果你有具体的 GC 日志、OOM 错误,或者想了解如何为你的应用选择合适的 GC 策略,欢迎提供更多信息,我可以给出针对性的优化建议!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值