深入理解 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 原理和策略,有助于调优程序性能,避免内存溢出等问题。

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