Java虚拟机之垃圾收集(一)

目录

一、如何判定对象"生死"?

[1. 引用计数算法(理论参考)](#1. 引用计数算法(理论参考))

[2. 可达性分析算法(JVM 实际使用)](#2. 可达性分析算法(JVM 实际使用))

[3. 对象的"缓刑"机制](#3. 对象的“缓刑”机制)

二、引用类型与回收策略

三、何时触发垃圾回收?

[1. 分代回收策略](#1. 分代回收策略)

[2. 手动触发与注意事项](#2. 手动触发与注意事项)

四、垃圾回收算法与实现

[1. 基础算法对比](#1. 基础算法对比)

[2. 分代收集理论](#2. 分代收集理论)

[3. 新生代回收:Apple式复制算法](#3. 新生代回收:Apple式复制算法)

五、主流垃圾收集器详解

[1. CMS 收集器(低停顿优先)](#1. CMS 收集器(低停顿优先))

[2. G1 收集器(平衡吞吐与延迟)](#2. G1 收集器(平衡吞吐与延迟))

[3. 收集器对比](#3. 收集器对比)

六、调优建议与工具推荐

[1. 参数配置示例](#1. 参数配置示例)

[2. 常见问题排查](#2. 常见问题排查)

[3. 工具推荐](#3. 工具推荐)

七、总结


一、如何判定对象"生死"?

垃圾收集(GC)的核心是识别无用对象。JVM 通过两种算法判断对象是否存活:

1. 引用计数算法(理论参考)

  • 原理

    每个对象维护一个引用计数器,被引用时计数器 +1,引用失效时 -1。计数器为 0 时判定为可回收。

  • 缺点

    无法解决循环引用问题(如对象 A 引用 B,B 也引用 A)。

  • Java 未采用 :主流 JVM 均使用 可达性分析算法

2. 可达性分析算法(JVM 实际使用)

  • 原理

    GC Roots 出发,遍历对象引用链。若对象无法被 GC Roots 关联,则判定为可回收。

  • GC Roots 对象类型

    • 虚拟机栈中的局部变量(如方法参数、局部变量)。

    • 方法区中静态变量引用的对象。

    • 方法区中常量引用的对象(如字符串常量池)。

    • 本地方法栈中 JNI 引用的对象(Native 方法)。

    • 同步锁持有的对象(synchronized 锁对象)。

    • Java 虚拟机内部对象(如系统类加载器、异常对象)。

3. 对象的"缓刑"机制

  • finalize() 方法

    若对象重写 finalize() 且未被调用过,JVM 会将其放入 F-Queue,由 Finalizer 线程触发该方法。

  • 逃脱机会

    finalize() 中重新建立与 GC Roots 的引用链,可避免被回收(仅一次)。

java 复制代码
public class RescueObject {
    public static RescueObject hook;

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        hook = this; // 在 finalize 中自我拯救
    }
}

二、引用类型与回收策略

Java 提供 四种引用类型,控制对象生命周期与回收优先级:

引用类型 特点 回收时机 典型场景
强引用 Object obj = new Object(),默认引用类型 对象不可达时回收 普通对象创建
软引用 SoftReference<Object> ref = new SoftReference<>(obj) 内存不足时回收(OOM 前触发) 缓存(如图片缓存)
弱引用 WeakReference<Object> ref = new WeakReference<>(obj) 下一次 GC 时回收 临时缓存(如 WeakHashMap)
虚引用 PhantomReference<Object> ref = new PhantomReference<>(obj, queue) 随时可能回收,需配合 ReferenceQueue 使用 堆外内存回收监听(如 DirectByteBuffer)

三、何时触发垃圾回收?

GC 触发时机由 内存区域分配策略JVM 配置参数 共同决定:

1. 分代回收策略

区域 GC 类型 触发条件
新生代 Minor GC Eden 区空间不足
老年代 Major GC 老年代空间不足(通常伴随 Full GC)
整堆 Full GC 方法区不足、老年代空间不足、手动调用 System.gc()

2. 手动触发与注意事项

  • System.gc() :建议 JVM 触发 Full GC(不保证立即执行)。

  • 风险:频繁 Full GC 会导致应用停顿(Stop-The-World),需谨慎使用。


四、垃圾回收算法与实现

1. 基础算法对比

算法 步骤 优点 缺点 适用场景
标记-清除 标记存活对象 → 清除未标记对象 简单 内存碎片化 老年代(CMS)
复制算法 存活对象复制到新区域 → 清空原区域 无碎片,高效 内存利用率 50% 新生代(Survivor)
标记-整理 标记存活对象 → 整理到内存一端 无碎片化 整理耗时 老年代(Serial Old)

2. 分代收集理论

  • 弱分代假说:绝大多数对象朝生夕灭(新生代)。

  • 强分代假说:熬过多次 GC 的对象难以消亡(老年代)。

  • 分代设计

    • 新生代:使用复制算法(Eden + Survivor)。

    • 老年代:使用标记-清除或标记-整理算法。

3. 新生代回收:Apple式复制算法

  • 内存划分

    • Eden : Survivor1 : Survivor2 = 8:1:1(默认)。
  • 回收流程

    1. 新对象分配至 Eden 区。

    2. Eden 满时触发 Minor GC,存活对象复制到 Survivor1。

    3. 下次 Minor GC 时,Eden 和 Survivor1 存活对象复制到 Survivor2,并清空原区域。

    4. 对象年龄达到阈值(默认 15)后晋升老年代。


五、主流垃圾收集器详解

1. CMS 收集器(低停顿优先)

  • 目标:最小化应用停顿时间。

  • 算法:标记-清除。

  • 工作流程

    1. 初始标记(STW):标记 GC Roots 直接关联对象。

    2. 并发标记:遍历对象图(与用户线程并发)。

    3. 重新标记(STW):修正并发标记期间变动的引用。

    4. 并发清除:清理垃圾(与用户线程并发)。

  • 缺点

    • 内存碎片化(需定期 Full GC 整理)。

    • 并发阶段占用 CPU 资源。

2. G1 收集器(平衡吞吐与延迟)

  • 目标:可预测的停顿时间(如 200ms 内)。

  • 内存布局:将堆划分为多个 Region(默认 2048 个)。

  • 工作流程

    1. 初始标记(STW):标记 GC Roots 直接关联对象。

    2. 并发标记:遍历对象图(与用户线程并发)。

    3. 最终标记(STW):处理剩余引用变更。

    4. 筛选回收(STW):选择性价比高的 Region 回收。

  • 优势

    • 支持大内存(TB 级)。

    • 通过 Region 划分减少碎片化。

3. 收集器对比

收集器 算法 区域 特点 适用场景
CMS 标记-清除 老年代 低停顿,但碎片化严重 响应敏感型应用
G1 标记-整理 全堆 可预测停顿,兼顾吞吐与延迟 大内存、低延迟应用

六、调优建议与工具推荐

1. 参数配置示例

bash 复制代码
# 使用 G1 收集器,堆内存 4G,目标停顿 200ms
java -Xmx4G -Xms4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar app.jar

# 启用 CMS 收集器
-XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode

2. 常见问题排查

  • 频繁 Full GC

    • 检查内存泄漏(如静态集合未清理)。

    • 调整新生代与老年代比例(-XX:NewRatio)。

  • 长时间 STW

    • 切换低延迟收集器(如 G1/ZGC)。

    • 减少堆内存大小(权衡吞吐与停顿)。

3. 工具推荐

  • 监控工具:VisualVM、JConsole、Prometheus + Grafana。

  • 日志分析:GCeasy、GCViewer。

  • 诊断工具:Arthas、MAT(Memory Analyzer Tool)。


七、总结

  • 生死判定 :可达性分析是核心,finalize() 是最后的逃生机会。

  • 引用分级:软、弱引用优化内存敏感场景。

  • 算法选择:分代理论平衡效率与资源利用率。

  • 收集器选型:CMS 适合低延迟,G1 适合大内存与可预测停顿。

核心原则:结合业务需求与监控数据动态调优,避免盲目配置。

相关推荐
夏天的味道٥2 小时前
使用 Java 执行 SQL 语句和存储过程
java·开发语言·sql
IT、木易3 小时前
大白话JavaScript实现一个函数,将字符串中的每个单词首字母大写。
开发语言·前端·javascript·ecmascript
冰糖码奇朵4 小时前
大数据表高效导入导出解决方案,mysql数据库LOAD DATA命令和INTO OUTFILE命令详解
java·数据库·sql·mysql
好教员好4 小时前
【Spring】整合【SpringMVC】
java·spring
Mr.NickJJ4 小时前
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
开发语言·javascript·react.js
浪九天5 小时前
Java直通车系列13【Spring MVC】(Spring MVC常用注解)
java·后端·spring
Archer1945 小时前
C语言——链表
c语言·开发语言·链表
My Li.5 小时前
c++的介绍
开发语言·c++
堕落年代5 小时前
Maven匹配机制和仓库库设置
java·maven
功德+n5 小时前
Maven 使用指南:基础 + 进阶 + 高级用法
java·开发语言·maven