G1 垃圾收集器

🔹 1. G1 的设计目标

吞吐量和低延迟之间取得平衡,尤其适合 **大堆内存(数 GB 甚至几十 GB)**的应用。

以往的收集器问题:

  • CMS:低延迟,但会有内存碎片、并发失败等问题。
  • Parallel GC:吞吐量高,但可能产生长时间 STW(Stop-The-World)。

👉 G1 的目标:在可控停顿时间内,尽可能回收尽量多的垃圾


🔹 2. G1 的内存布局

和传统 GC 最大不同:

  • 传统 GC 把堆分成 新生代(Eden、Survivor)+ 老年代
  • G1 把整个堆切分成大小相等的 Region(区域),每个 Region 大小一般是 1~32MB(JVM 自动选择)。

特点:

  • 每个 Region 可以动态扮演不同角色(Eden、Survivor、Old、Humongous)。
  • Humongous Region 用来存放超大对象(> 1/2 Region 大小)。

🔹 3. G1 的垃圾回收过程

(1)年轻代收集(Young GC)

  • 回收 Eden + Survivor 区域。
  • STW,使用 并行复制算法,幸存对象复制到 Survivor/Old。

(2)并发标记(Concurrent Marking)

  • 类似 CMS,G1 在后台对 Old 区进行并发可达性分析。
  • 标记哪些 Region 是垃圾最多的候选清理对象。

(3)混合收集(Mixed GC)

  • 不仅回收年轻代,还选取部分垃圾比例高的老年代 Region 一起回收。
  • 这是 G1 的关键:分阶段清理老年代,避免一次性 Full GC。

(4)Full GC(少见)

  • 极端情况下(比如 Humongous 对象太多),仍然可能触发 单线程 Full GC,性能差。

🔹 4. G1 的核心优势

  1. 分区(Region)+ 局部收集

    • 不必一次性清理整个老年代,而是挑选垃圾最多的 Region,回收效率高。
  2. 可预测的停顿时间

    • JVM 参数可以指定 -XX:MaxGCPauseMillis=N,G1 会尽量满足。
  3. 内存整合

    • 采用 复制算法,天然避免碎片问题(CMS 会有碎片)。

🔹 5. 常用 JVM 参数

# 使用 G1
-XX:+UseG1GC

# 期望的最大停顿时间(默认 200ms)
-XX:MaxGCPauseMillis=200

# 启动并发 GC 的阈值(老年代占堆比例,默认 45%)
-XX:InitiatingHeapOccupancyPercent=45

# Region 大小(一般不用手动设,默认自动选择)
-XX:G1HeapRegionSize=n

🔹 6. 缺点

  • 实现复杂,调优成本比 Parallel/CMS 高。
  • 吞吐量比 Parallel GC 略低。
  • 仍然可能出现 Full GC(但概率比 CMS 小)。

✅ 总结

  • G1 = Garbage First:优先回收垃圾最多的 Region。
  • 适合大内存场景,支持停顿时间可预测。
  • 内存碎片少,老年代分步回收,不容易产生长时间 STW。

每个 Region 可以动态扮演不同角色(Eden、Survivor、Old、Humongous) ——这个是每个region中都这几个部分,还是单独每个region就是eden或者survivor

G1 垃圾收集器 中:


1. Region 的基本概念

  • G1 把整个堆划分为许多个大小相同的 Region(通常是 1MB~32MB,不可变)。

  • 每个 Region 只能有一种角色,即:

    • Eden Region
    • Survivor Region
    • Old Region
    • Humongous Region(专门存放大对象,大对象跨多个 Region)

也就是说:

  • Region 不是内部划分 Eden/Survivor/Old 的“分区”,
  • 而是 一个 Region 整体就是某个角色

2. 动态角色转换

  • G1 运行过程中,JVM 会根据内存使用情况 动态地改变 Region 的角色
  • 例如:某个 Region 原来是 Eden,当 Minor GC 之后,里面存活的对象可能会晋升 → 这个 Region 就会变成 SurvivorOld
  • 分配新对象时,JVM 也会从空闲 Region 池里挑一些 Region 当作 Eden。

3. 举个例子

假设堆被划分为 2000 个 Region:

  • 其中 300 个当前是 Eden
  • 100 个当前是 Survivor
  • 1400 个当前是 Old
  • 200 个空闲备用

GC 过程结束后,角色可能会变化,比如 Eden 中存活下来的数据挪到 Survivor/Old,然后原来的 Eden Region 清空,重新分配成 Eden。


结论

  • 一个 Region 只能属于 Eden/Survivor/Old/Humongous 其中之一,不会在一个 Region 内同时有多个部分。
  • Region 的角色是 动态可变的,GC 周期内可以转换。

那不就是和新生代回收是一样的呀?都是复制算法

对!🎯 你抓到重点了:

G1 新生代回收老年代回收在“整理思想”上确实是一致的:

  • 都是 复制存活对象到新的 Region释放原 Region
  • 所以可以说:G1 的核心就是基于 Region 的复制算法

🔹 新生代回收(Minor GC)

  • 只涉及 Eden → Survivor/Old。
  • 典型的 复制算法,所有 Eden 区的存活对象会被复制到 Survivor/Old。
  • 回收结束后,Eden Region 整体清空。

🔹 老年代回收(Mixed GC / Full GC)

  • 涉及 Old Region + 年轻代。
  • G1 不会像 CMS 一样在老年代内部做原地整理,而是 选择一批“回收价值高”的 Old Region(垃圾比例高的),把其中的存活对象复制到新的 Region(Survivor 或其他 Old Region)。
  • 原 Old Region 整体释放 → 避免碎片化。

🔹 为什么说 G1 的老年代回收 ≠ 传统复制算法?

区别在于:

  1. 对象规模:新生代对象小、短命,复制代价低;老年代对象大、长命,直接全盘复制开销会非常高。

    • G1 的做法是 按 Region 选择性复制,只收一部分 Old Region(增量回收)。
    • 所以你会看到 G1 的 GC 过程叫 Mixed GC,不是全量回收。
  2. 停顿控制

    • 新生代复制 → 一次 Minor GC 就能搞定。
    • 老年代复制 → 可能一次 GC 只搬一部分 Region,下次 GC 再搬另一部分。这样分批进行,达到可预测的停顿时间。

总结

  • G1 新生代和老年代回收 都基于复制思想

  • 区别在于:

    • 新生代 → 全量复制。
    • 老年代 → 按 Region 局部复制(增量式),避免一次性停顿太长。

那为什么还说g1针对老年代是标记整理算法?

这个点很容易混淆 👍,我来帮你彻底捋清楚:


🔹 为什么说 G1 针对老年代是“标记-整理算法”?

  1. 从整体视角(算法分类)看

    • G1 在老年代回收时,会先做 全堆并发标记 → 找出所有存活对象。
    • 然后执行 整理(Compact):把存活对象移动到其他 Region,释放原来的 Region。
    • 这符合经典 GC 三部曲:标记(Mark) → 整理(Compact),所以称为 标记-整理(Mark-Compact)算法

  1. 和传统“整理”不同

    • 传统的 标记-整理(如 Serial Old、Parallel Old)是在 同一块连续内存里移动对象,把碎片挤压到一边。

    • 而 G1 的整理是 以 Region 为单位

      • 并不是在 Region 内部做“就地挤压”,
      • 而是 把存活对象复制到别的 Region,让整个 Region 变为空闲。

    👉 所以从实现细节看,它其实用的是 复制(Copying)


  1. 为什么不用“复制算法”来概括老年代?

    • 新生代是“复制收集器”的典型代表(Eden → Survivor/Old 全量复制)。
    • 老年代对象体量大、寿命长,不能像新生代那样每次都全量复制。
    • G1 的老年代回收更贴近“先标记,再局部复制搬迁”,核心目标是整理碎片,而不是像新生代那样纯粹为了回收。
    • 因此在学术和官方文档里,通常称 G1 老年代是 标记-整理,而不是简单粗暴地说“复制”。

🔹 总结一句话

  • 叫“标记-整理”是从算法分类和效果出发:先标记存活对象,再通过跨 Region 搬迁完成内存整理。
  • 实现细节上,它采用的是复制(Copying)技术来完成整理。

所以可以理解为:
👉 G1 老年代的标记-整理 = 标记 + 基于复制的整理

finalize方法是干嘛的

finalize() 方法是 Java 中的一种对象“临终清理”机制,在对象被垃圾回收(GC)前,JVM 有机会调用它执行一些清理操作。但它现在已经几乎被废弃(Deprecated)

我们先从原理讲起👇


💡 一、finalize() 的作用

finalize()java.lang.Object 类中的一个方法:

protected void finalize() throws Throwable { }

作用是:

当垃圾回收器(GC)准备回收某个对象时,如果该对象重写了 finalize() 方法,JVM 会在回收前调用一次该方法,让你有机会“临终前”清理资源(例如关闭文件、释放内存、断开网络连接等)。


🧠 举个例子

public class Demo {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象被回收前执行清理...");
    }

    public static void main(String[] args) {
        new Demo();
        System.gc();  // 主动建议进行垃圾回收
    }
}

运行后可能输出:

对象被回收前执行清理...

⚠️ 二、为什么不推荐使用 finalize()

虽然听起来有用,但它的实际问题非常多:

问题说明
⚡ 不确定性你不知道 什么时候 会调用(甚至可能永远不会被调用)。GC 时间是不可预测的。
🧟‍♂️ 延迟回收如果对象在 finalize() 里“复活”了(把自己重新引用起来),GC 会推迟回收,导致内存泄漏。
❌ 性能开销JVM 调用 finalize() 要额外维护一个队列,效率低。
🧯 更好的替代方案Java 提供了更安全可靠的资源释放方式,比如 try-with-resourcesAutoCloseable 接口

✅ 三、推荐替代方案:AutoCloseable + try-with-resources

现代 Java 推荐使用 自动资源管理(ARM)

class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("资源已关闭");
    }
}

public class Test {
    public static void main(String[] args) {
        try (MyResource r = new MyResource()) {
            System.out.println("使用资源中...");
        } // 退出 try 块时自动调用 close()
    }
}

输出:

使用资源中...
资源已关闭

这种方式可控、及时、安全,完全取代 finalize() 的用途。


📜 四、总结

项目说明
所属类java.lang.Object
方法签名protected void finalize() throws Throwable
调用时机对象被 GC 回收前,由 JVM 调用
作用清理资源(早期用法)
当前状态已弃用(从 Java 9 开始 Deprecated)
推荐替代AutoCloseable + try-with-resources
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值