深入理解 Java 虚拟机之垃圾收集

垃圾收集(Garbage Collection,GC)是 Java 虚拟机(JVM)内存管理的核心机制,主要针对 Java 堆方法区 进行回收。线程私有的程序计数器、虚拟机栈和本地方法栈随线程生命周期结束而自然消失,无需 GC。本文将详细探讨 GC 的判定依据、算法、收集器及内存分配策略。


对象是否需要回收

1. 引用计数算法

  • 原理:对象维护一个引用计数器,被引用时加 1,引用失效时减 1,计数为 0 时可回收。
  • 缺陷:无法解决循环引用问题,例如两个对象互相引用,计数永不为 0。
  • 结论:JVM 不采用此算法。

2. 可达性分析算法

  • 原理 :从 GC Roots 开始搜索,可达对象存活,不可达对象死亡。
  • GC Roots
    • 虚拟机栈中的引用对象。
    • 本地方法栈中的引用对象。
    • 方法区中的静态属性和常量引用对象。
  • 应用:JVM 主流采用此算法。

3. 引用类型

Java 提供四种引用类型,影响对象回收:

  • 强引用new 创建的对象,不会回收。
  • 软引用SoftReference,内存不足时回收。
  • 弱引用WeakReference,下次 GC 时回收,常用于缓存(如 WeakHashMap)。
  • 虚引用PhantomReference,仅用于接收回收通知。

4. 方法区回收

  • 目标:废弃常量和无用类。
  • 类卸载条件
    1. 所有实例已回收。
    2. 类加载器已回收。
    3. Class 对象未被引用。
  • 控制 :通过 -Xnoclassgc 参数决定是否卸载。

5. finalize() 方法

  • 作用:对象回收前可执行的自救方法。
  • 问题:运行代价高、不确定性大,建议避免使用。

垃圾收集算法

1. 性能指标

  • 停顿时间:GC 导致的程序暂停时长。
  • 吞吐量:用户代码运行时间占比。

2. 标记-清除(Mark-Sweep)

  • 过程:标记需回收对象,然后清除。
  • 缺点:效率低,产生内存碎片。

3. 标记-整理(Mark-Compact)

  • 过程:标记后将存活对象移至一端,清理边界外内存。
  • 优点:解决碎片问题。
  • 缺点:整理开销大。

4. 标记-复制(Copying)

  • 过程:内存分两块,存活对象复制到空闲块,清理已用块。
  • 优点:无碎片。
  • 缺点:内存利用率低(改进为 Eden + 2 Survivor)。

5. 分代收集

  • 原理:根据对象生命周期分代,年轻代用复制算法,老年代用标记-清除或标记-整理。
  • 堆结构
    • 新生代:Eden + 2 Survivor,默认比例 8:1:1。
    • 老年代:存放长生命周期对象。
    • 永久代:JDK 8 前的方法区实现,现为元空间。

垃圾收集器

HotSpot 提供多种垃圾收集器,各有适用场景:

1. 串行收集器(Serial)

  • 特点:单线程,Stop-The-World,适用于 Client 模式。
  • 组合
    • Serial :年轻代,复制算法,-XX:+UseSerialGC
    • Serial Old:老年代,标记-整理。

2. 并行收集器

  • 目标:高吞吐量,Server 模式默认。
  • 组合
    • Parallel Scavenge :年轻代,复制算法,-XX:+UseParallelGC
    • Parallel Old :老年代,标记-整理,-XX:+UseParallelOldGC
  • 参数
    • -XX:MaxGCPauseMillis:控制停顿时间。
    • -XX:GCTimeRatio:设置吞吐量。
    • -XX:+UseAdaptiveSizePolicy:自适应调整。

3. 并发标记清除(CMS)

  • 目标:最短停顿时间。
  • 组合-XX:+UseConcMarkSweepGC,ParNew(年轻代)+ CMS(老年代)+ Serial Old(备用)。
  • 步骤
    1. 初始标记(停顿)。
    2. 并发标记。
    3. 重新标记(停顿)。
    4. 并发清除。
  • 缺点:内存碎片、浮动垃圾,需预留空间。

4. G1 收集器

  • 特点 :兼顾吞吐量和停顿时间,JDK 9+ 默认,-XX:+UseG1GC
  • 分区:堆划分为多个 Region,动态分配角色。
  • 步骤
    1. 初始标记。
    2. 并发标记。
    3. 最终标记。
    4. 筛选回收(优先高价值 Region)。
  • 优点:无碎片,可预测停顿。

收集器对比

收集器 类型 算法 目标 场景
Serial 串行 年轻代 复制 低停顿 单核 Client 模式
Serial Old 串行 老年代 标记-整理 低停顿 CMS 备用
ParNew 并行 年轻代 复制 低停顿 与 CMS 配合
Parallel Scavenge 并行 年轻代 复制 高吞吐量 后台运算
Parallel Old 并行 老年代 标记-整理 高吞吐量 后台运算
CMS 并发 老年代 标记-清除 低停顿 Web 服务端
G1 并发 全部 标记-整理+复制 低停顿+吞吐量 服务端应用

内存分配与回收策略

1. Minor GC

  • 触发:Eden 区满。
  • 过程 :存活对象复制到 Survivor,年龄达阈值(默认 15,-XX:MaxTenuringThreshold)晋升老年代。

2. Full GC

  • 触发条件
    1. System.gc()(建议性,可禁用)。
    2. 老年代空间不足。
    3. 方法区(元空间)不足。
    4. Minor GC 晋升平均大小超老年代剩余空间。
    5. 对象大于 Survivor 和老年代可用空间。

3. 分配策略

  • Eden 优先:新对象分配在 Eden。
  • 大对象直入老年代-XX:PretenureSizeThreshold
  • 长期存活晋升:年龄超阈值。
  • 动态年龄判定:Survivor 半满时提前晋升。
  • 空间担保:老年代为 Minor GC 提供担保。

结语

JVM 的垃圾收集机制通过可达性分析、分代收集和多样化的收集器,高效管理内存。串行适合小型应用,并行追求吞吐量,CMS 和 G1 优化停顿时间。理解 GC 原理和策略,有助于调优程序性能,避免内存溢出等问题。

相关推荐
楚Y6同学几秒前
基于 Haversine 公式实现【经纬度坐标点】球面距离计算(C++/Qt 实现)
开发语言·c++·qt·经纬度距离计算
你怎么知道我是队长16 分钟前
C语言---缓冲区
c语言·开发语言
一勺菠萝丶25 分钟前
PDF24 转图片出现“中间横线”的根本原因与终极解决方案(DPI 原理详解)
java
姓蔡小朋友29 分钟前
Unsafe类
java
一只专注api接口开发的技术猿43 分钟前
如何处理淘宝 API 的请求限流与数据缓存策略
java·大数据·开发语言·数据库·spring
superman超哥44 分钟前
Rust 异步递归的解决方案
开发语言·后端·rust·编程语言·rust异步递归
荒诞硬汉44 分钟前
对象数组.
java·数据结构
期待のcode1 小时前
Java虚拟机的非堆内存
java·开发语言·jvm
黎雁·泠崖1 小时前
Java入门篇之吃透基础语法(二):变量全解析(进制+数据类型+键盘录入)
java·开发语言·intellij-idea·intellij idea