Java垃圾回收机制深度解析:从原理到实战

🔥你好我是fengxin_rou这是我的个人主页 fengxin_rou的主页

❄️欢迎查看我的专栏我的专栏

《Java后端学习》《JAVASE基础》《JUC并发》《redis》《JVM虚拟机》《MYSQL》《黑马点评》《rabbitmq》《JavaWeb+AI的talis学习系统》《苍穹外卖》

目录

[一、对象存活判断:引用计数 vs 可达性分析](#一、对象存活判断:引用计数 vs 可达性分析)

[1.1 引用计数法(Reference Counting)](#1.1 引用计数法(Reference Counting))

[1.2 可达性分析法(Reachability Analysis)](#1.2 可达性分析法(Reachability Analysis))

[二、GC Roots详解:哪些对象可以作为根节点?](#二、GC Roots详解:哪些对象可以作为根节点?)

三、三大垃圾回收算法深度对比

[3.1 标记-清除算法(Mark-Sweep)](#3.1 标记-清除算法(Mark-Sweep))

[3.2 标记-整理算法(Mark-Compact)](#3.2 标记-整理算法(Mark-Compact))

[3.3 复制算法(Copying)](#3.3 复制算法(Copying))

四、分代收集策略:为什么新生代用复制算法,老年代用标记-整理?

[4.1 新生代(Young Generation)](#4.1 新生代(Young Generation))

[4.2 老年代(Old Generation)](#4.2 老年代(Old Generation))

五、新生代对象晋升老年代的5种条件

[六、GC类型详解:Minor GC、Major GC、Full GC](#六、GC类型详解:Minor GC、Major GC、Full GC)

[6.1 Minor GC(Young GC)](#6.1 Minor GC(Young GC))

[6.2 Major GC](#6.2 Major GC)

[6.3 Full GC](#6.3 Full GC)

[七、STW(Stop The World)机制详解](#七、STW(Stop The World)机制详解)

[7.1 什么是STW?](#7.1 什么是STW?)

[7.2 STW发生时机](#7.2 STW发生时机)

八、三色标记法:并发标记的核心算法

[8.1 三色标记法原理](#8.1 三色标记法原理)

[8.2 标记流程示例](#8.2 标记流程示例)

[8.3 并发标记问题](#8.3 并发标记问题)

[8.4 解决方案:写屏障](#8.4 解决方案:写屏障)

九、常用垃圾收集器概览

十、GC调优实战建议

[10.1 监控工具](#10.1 监控工具)

[10.2 调优原则](#10.2 调优原则)

[10.3 常见问题排查](#10.3 常见问题排查)

十一、总结与最佳实践

核心要点

最佳实践

十二、常见问题解答(FAQ)

Q1:为什么要有垃圾回收?

[Q2:Minor GC和Full GC哪个更可怕?](#Q2:Minor GC和Full GC哪个更可怕?)

Q3:如何判断内存泄漏?

Q4:ZGC为什么能做到超低延迟?

Q5:G1和CMS有什么区别?

参考文献

本文亮点


引言:垃圾回收(Garbage Collection, GC)是Java虚拟机(JVM)自动内存管理的核心机制,它解放了开发者手动管理内存的负担,但同时也带来了性能调优的挑战。理解GC机制不仅能帮助开发者编写更高效的代码,还能在系统出现性能瓶颈时快速定位问题。本文将深入剖析Java垃圾回收的原理、算法和实战调优策略。

一、对象存活判断:引用计数 vs 可达性分析

1.1 引用计数法(Reference Counting)

原理:为每个对象分配一个计数器,被引用时+1,引用失效时-1,计数器为0时即可回收。

致命缺陷:循环引用问题。当两个对象互相引用但不再被其他对象引用时,计数器永远为1,无法被回收。

java 复制代码
// 循环引用示例
public class CircularReference {
    public Object ref;
    public static void main(String[] args) {
        CircularReference a = new CircularReference();
        CircularReference b = new CircularReference();
        a.ref = b;  // a引用b
        b.ref = a;  // b引用a
        a = null;   // 断开栈引用
        b = null;   // 断开栈引用
        // 此时a和b对象互相引用,但无法被回收
    }
}

1.2 可达性分析法(Reachability Analysis)

原理:从GC Roots出发,通过引用链遍历所有可达对象,不可达对象即可回收。

优势 :解决了循环引用问题,是现代JVM的主流选择。

二、GC Roots详解:哪些对象可以作为根节点?

GC Roots是可达性分析的起点,包括以下6类:

  1. 虚拟机栈中的引用:栈帧中的局部变量表引用的对象
  2. 方法区静态属性:类静态变量引用的对象
  3. 方法区常量池:常量引用的对象
  4. 本地方法栈:JNI(Java Native Interface)引用的对象
  5. 同步锁对象:被synchronized持有的对象
  6. JVM内部引用:基本数据类型的Class对象、类加载器、常驻异常等

三、三大垃圾回收算法深度对比

3.1 标记-清除算法(Mark-Sweep)

工作流程

  1. 标记阶段:标记所有存活对象
  2. 清除阶段:回收未被标记的对象

优点:实现简单,不需要移动对象

缺点

  • 产生内存碎片,大对象分配困难
  • 标记和清除效率不高
  • 分配内存时需要维护空闲链表

3.2 标记-整理算法(Mark-Compact)

工作流程

  1. 标记阶段:标记所有存活对象
  2. 整理阶段:将存活对象向一端移动,清理边界外内存

优点:无内存碎片,内存利用率高

缺点:移动对象有性能开销,需要更新引用

3.3 复制算法(Copying)

工作流程

  1. 将内存分为两块(From和To)
  2. 只使用From区,GC时将存活对象复制到To区
  3. 清空From区,交换From和To角色

优点

  • 无内存碎片
  • 适合存活率低的场景(新生代)
  • 分配内存只需移动指针

缺点:内存利用率只有50%

四、分代收集策略:为什么新生代用复制算法,老年代用标记-整理?

4.1 新生代(Young Generation)

特点:对象生命周期短,存活率低(约98%对象朝生夕死)

内存布局

  • Eden区:80%,新对象首先分配在此
  • Survivor区:20%(From 10% + To 10%),存放每次GC后的存活对象

GC流程

  1. 新对象分配在Eden区
  2. Eden满时触发Minor GC
  3. 存活对象复制到From区,年龄+1
  4. 后续Minor GC在From和To区之间复制
  5. 年龄达到阈值(默认15)晋升老年代

4.2 老年代(Old Generation)

特点:对象生命周期长,存活率高

算法选择:标记-整理算法(避免复制算法的高复制成本)

  1. 年龄达到阈值:默认15岁(-XX:MaxTenuringThreshold)
  2. 动态年龄判断:相同年龄对象总大小 > Survivor区50%,直接晋升
  3. 大对象直接晋升:超过-XX:PretenuringSizeThreshold的对象直接进入老年代
  4. Survivor空间不足:Minor GC后存活对象超过Survivor可用空间
  5. 空间担保机制:老年代剩余空间 < 新生代存活对象预估大小时,触发Full GC

六、GC类型详解:Minor GC、Major GC、Full GC

6.1 Minor GC(Young GC)

作用范围:新生代(Eden + Survivor)

触发条件:Eden区空间不足

特点

  • 触发频率高
  • 回收效率高(存活对象少)
  • 暂停时间短(通常<50ms)

6.2 Major GC

作用范围:老年代

触发条件:老年代空间不足或晋升速度过快

特点

  • 触发频率较低
  • 回收时间较长(存活对象多)
  • 可能伴随Minor GC

6.3 Full GC

作用范围:整个Java堆 + 方法区/元空间

触发条件

  • System.gc()调用(建议执行,不保证)
  • 空间分配担保失败
  • 元空间不足
  • CMS并发失败(Concurrent Mode Failure)

特点

  • 最昂贵的操作,全程STW
  • 需要遍历整个堆
  • 应尽量避免和减少

七、STW(Stop The World)机制详解

7.1 什么是STW?

JVM执行关键操作时强制暂停所有应用线程,只有GC线程工作。这是所有带GC语言的共性,不是Java独有。

7.2 STW发生时机

GC相关

  • 串行GC、并行GC:全程STW
  • CMS、G1:初始标记和重新标记阶段STW(时间短)
  • ZGC、Shenandoah:几乎全并发,STW<10ms

其他JVM操作

  • 类卸载
  • 代码反优化
  • 偏向锁撤销
  • 堆转储(Heap Dump)
  • 安全点操作(SafePoint)

八、三色标记法:并发标记的核心算法

8.1 三色标记法原理

三色标记法是可达性分析的并发实现,解决传统串行标记的长时间STW问题。

三种颜色状态

  1. 白色:未被扫描,标记结束后仍为白色则为垃圾
  2. 灰色:已被扫描,但子对象未遍历完
  3. 黑色:已被扫描,子对象也全部遍历完

8.2 标记流程示例

GC Roots → A → B → C为例:

  1. 初始化:所有对象为白色
  2. 根扫描:A标记为灰色
  3. 遍历灰色队列
    • A引用B → B标记为灰色,A变黑
    • B引用C → C标记为灰色,B变黑
    • C无引用 → C变黑
  4. 标记结束:灰色队列为空,白色对象为垃圾

8.3 并发标记问题

多标(浮动垃圾)

  • 对象被标记为黑色后,业务线程断开引用
  • 影响:少量浮动垃圾,下轮GC清理

漏标(致命错误)

  • 存活对象被误判为垃圾回收
  • 产生条件(同时满足):
    1. 黑色对象新增指向白色对象的引用
    2. 所有灰色对象到白色对象的引用被切断

8.4 解决方案:写屏障

强三色不变性:黑色对象不能直接引用白色对象(增量更新)

弱三色不变性:黑色对象可通过灰色对象间接引用白色对象(SATB)

九、常用垃圾收集器概览

收集器 作用区域 算法 特点 适用场景
Serial 新生代 复制 单线程,简单高效 客户端模式
ParNew 新生代 复制 多线程版本Serial 配合CMS
Parallel Scavenge 新生代 复制 关注吞吐量 后台计算
Serial Old 老年代 标记-整理 单线程 客户端模式
Parallel Old 老年代 标记-整理 多线程,关注吞吐量 后台计算
CMS 老年代 标记-清除 低延迟 Web应用
G1 全堆 混合 Region化,可预测停顿 大内存应用
ZGC 全堆 染色指针 超低延迟(<10ms) 大内存低延迟

十、GC调优实战建议

10.1 监控工具

  • jstat:GC统计信息
  • jmap:堆转储分析
  • VisualVM:图形化监控
  • GC日志分析工具(GCEasy、GCViewer)

10.2 调优原则

  1. 优先选择合适的收集器:根据应用特点选择
  2. 合理设置堆大小:-Xms和-Xmx设置相同
  3. 新生代比例调整:-XX:NewRatio
  4. 晋升阈值调整:-XX:MaxTenuringThreshold
  5. 避免Full GC:监控老年代使用率

10.3 常见问题排查

  • 频繁Full GC:检查内存泄漏、老年代设置过小
  • 长时间停顿:检查GC日志,调整收集器参数
  • 内存溢出:分析堆转储,查找内存泄漏点

十一、总结与最佳实践

核心要点

  1. 理解分代思想:根据对象生命周期特点采用不同策略
  2. 掌握三大算法:标记-清除、标记-整理、复制算法各有优劣
  3. 熟悉收集器特性:根据应用场景选择合适的收集器
  4. 关注STW影响:通过调优减少停顿时间
  5. 善用监控工具:通过数据驱动调优决策

最佳实践

  1. 生产环境必须开启GC日志:-Xlog:gc*:file=gc.log
  2. 根据应用特点选择收集器:低延迟用G1/ZGC,高吞吐用Parallel
  3. 定期分析GC日志,发现潜在问题
  4. 避免在代码中调用System.gc()
  5. 注意大对象分配对GC的影响

十二、常见问题解答(FAQ)

Q1:为什么要有垃圾回收?

A:手动管理内存容易导致内存泄漏和悬空指针,GC自动回收不再使用的内存,提高开发效率和程序稳定性。

Q2:Minor GC和Full GC哪个更可怕?

A:Full GC更可怕。Minor GC只回收新生代,速度快;Full GC回收整个堆,耗时长,会导致长时间停顿。

Q3:如何判断内存泄漏?

A:通过监控工具观察老年代内存使用率持续增长,Full GC后内存释放很少,可能存在内存泄漏。

Q4:ZGC为什么能做到超低延迟?

A:ZGC使用染色指针、读屏障、多重映射等技术,几乎全部并发执行,STW时间<10ms。

Q5:G1和CMS有什么区别?

A:G1是区域化收集器,可预测停顿时间;CMS是老年代收集器,标记-清除算法有碎片问题。G1是CMS的替代者。

相关推荐
弗锐土豆2 小时前
使用eclipse、java、maven、j60870、oceanbase按照IEC104协议采集、存储电力数据
java·oceanbase·电表·iec104·抄表
小则又沐风a2 小时前
进程最终篇---进程控制(模拟实现xshell)
java·linux·服务器·前端
番石榴AI2 小时前
JiaJiaOCR-2.2.0:面向Java ocr的开源库
java·ocr
码云骑士2 小时前
【3.Java基础】Java运算符详解:从算数运算到逻辑判断,一篇文章全部掌握
java·开发语言
Web打印2 小时前
HttpPrinter web打印控件 官方文档(https://wiki.httpprinter.com/)快速检索目录
java·javascript·chrome
我登哥MVP2 小时前
Spring Boot 从“会用”到“精通”:内容协商原理
java·spring boot·后端·spring·java-ee·maven·lua
cfm_29142 小时前
Java JVM 零基础入门
java·jvm
兰令水2 小时前
leecodecode【状态机DP】【2026.6.9打卡-java版本】
java·开发语言·算法
我是一颗柠檬2 小时前
【Java项目技术亮点】接口限流熔断:从Sentinel到令牌桶/漏桶,手把手教你构建高可用服务防护体系
java·数据库·sentinel