全面理解 JVM 垃圾回收(GC)机制:原理、流程与实践

JVM 的 GC(Garbage Collection)机制是 Java 程序性能的关键支柱。本文将从堆内存布局、回收原理、GC 算法、流程细节、并发收集器机制等维度,系统讲清楚 GC 的底层运作原理和优化思路。

一、JVM 堆内存结构

Java 堆是 GC 管理的主要区域,按生命周期划分为以下几个部分:

1.1 年轻代(Young Generation)

  • Eden 区:新对象最初分配在此

  • Survivor 区:分为 S0 / S1 两个区,用于对象在晋升前的多次存活

1.2 老年代(Old Generation)

  • 存放从年轻代晋升上来的长寿对象

  • 内存大,GC 频率低,但成本高

1.3 元空间(MetaSpace)

  • 存放类元数据(Class 对象、方法表等)

  • 从 JDK 8 起替代了 PermGen 永久代

二、对象如何判断是否可以被回收?

2.1 可达性分析(Reachability Analysis)

GC 会从一组 “GC Roots” 对象开始向下追踪引用链,凡是无法从 GC Roots 可达的对象,被认为是“垃圾”。

GC Roots 主要包括:

  • 当前线程的局部变量(栈帧)

  • 类的静态字段引用

  • JNI 本地方法引用

2.2 引用计数法(已废弃)

虽然概念简单(对象被引用次数为 0 即可回收),但无法解决循环引用问题,因此主流 JVM 都使用可达性分析。

三、GC 类型与时机

3.1 Minor GC(小回收)

  • 回收年轻代

  • 使用 复制算法

  • 快速、频繁、STW

 3.2 Major GC / Old GC / Mixed GC(大回收)

  • 回收老年代

  • 可能是并发或 STW,取决于收集器

3.3 Full GC(整堆回收)

  • 回收整个堆(年轻代 + 老年代 + 元空间)

  • STW,性能开销大

  • 触发原因:

    • 老年代内存不足

    • 调用 System.gc()

    • CMS GC 失败

四、常见 GC 算法

4.1 复制算法(Copying)

  • 年轻代使用(Eden ➜ Survivor)

  • 每次回收只处理 Eden + 一个 Survivor,存活对象复制到另一个 Survivor 或晋升老年代

  • 简单高效,适合“朝生夕灭”的对象

4.2 标记-清除(Mark-Sweep)

  • 老年代基础算法

  • 缺点:产生碎片

4.3 标记-整理(Mark-Compact)

  • 在清除后整理内存,消除碎片

  • 用于老年代(如 Serial Old、G1 Old)

五、Stop-The-World (STW)

GC 期间 JVM 会暂停所有应用线程,这称为 Stop-The-World

即使是并发 GC(如 CMS、G1)也不能完全避免 STW,尤其在:

  • 初始标记(Initial Mark)

  • 重新标记(Remark)

  • Full GC

六、GC 的完整流程细节(以 G1 为例)

6.1 触发 GC(如 Eden 区满)

  1. Minor GC 开始

  2. STW,复制 Eden 区存活对象 ➜ Survivor 区

  3. Survivor 满或年龄够 ➜ 晋升老年代

  4. 释放 Eden 区对象

6.2 并发标记流程(预防 Full GC)

当老年代占用达到一定阈值时,G1 启动 Concurrent Marking 过程

阶段描述是否 STW
Initial Mark标记 GC Roots 引用对象✅ 是
Concurrent Mark遍历对象图,标记可达对象❌ 否
Remark捕获并发阶段变更的引用✅ 是
Cleanup清理不可达的 Region❌ 否

七、三色标记法(Tri-color Marking)

为支持并发标记,GC 使用三色标记算法确保安全性:

  • 白色:未被访问(假定为垃圾)

  • 灰色:被访问但未扫描其引用

  • 黑色:自身与其引用都已处理

 写屏障(SATB、增量更新)

用于记录并发阶段引用变更,防止“误删活对象”

八、对象晋升与回收细节

  • 对象在 Survivor 区每存活一次,年龄 +1

  • 达到 MaxTenuringThreshold 或 Survivor 区满时晋升老年代

  • G1 Mixed GC 会“部分回收”老年代,非 Full GC

九、避免 Full GC 的实践建议

  • 设置合适的堆大小:避免频繁 Minor 或 Full GC

  • 使用 -XX:+PrintGCDetails 观察 GC 频率和时间

  • 针对 G1,关注老年代使用率和 Mixed GC 是否充分触发

  • 防止大对象直接进入老年代(如 -XX:+UseLargePages、避免一次性创建大数组)

🔚 总结

JVM GC 机制既有实时性需求,也有吞吐压力。掌握对象生命周期、GC 类型和回收策略,是 Java 性能调优的核心技能。随着 G1、ZGC、Shenandoah 等收集器的引入,并发、可预测、低延迟的垃圾回收将成为主流。

🚫 使用 G1 GC 时的反面案例与问题分析

例 1:大量短命小对象 + 高分配速率,结果频繁 Minor GC

背景:

  • 某服务使用 G1 GC,业务为高并发 API 网关

  • 每秒创建数万个小对象(如 JSON 解析、临时集合等)

错误表现:

  • Eden 区很快满,导致 频繁 Minor GC

  • 老年代增长不快,但 GC 次数持续拉高

  • 响应时延抖动明显

原因分析:

G1 在默认配置下 Eden 占比较小,无法适应高速对象分配,导致频繁 GC(即便每次 GC 都很快)

优化建议:

-XX:G1NewSizePercent=30       # 提高年轻代初始比例
-XX:G1MaxNewSizePercent=60    # 增大年轻代最大值
-XX:MaxGCPauseMillis=100      # 适当放宽暂停时间目标

例 2:大对象直接分配到老年代,导致提前 Full GC

背景:

  • 某报表系统一次性构造数十 MB 的 List<Map<String, Object>>

  • JVM 使用 G1,老年代频繁增长

错误表现:

  • 年轻代未满,但大对象直接进老年代

  • 老年代迅速填满,触发 Full GC

  • G1 试图执行 Mixed GC,但回收效果不佳

原因分析:

  • G1 对大于 region size 的对象(默认 1~32MB),会直接分配到老年代

  • 如果这些对象生命周期短,会造成老年代“脏积压”

优化建议:

-XX:G1HeapRegionSize=8m           # 适当增大 Region Size
-XX:G1ReservePercent=20           # 增加保留内存,防止老年代爆掉
-XX:+UseStringDeduplication       # 优化大量重复字符串的内存使用

 例 3:设置了极端的 MaxGCPauseMillis,导致 GC 频繁打断业务

背景:

  • 运维强行要求 GC 停顿 < 20ms,于是配置:

-XX:MaxGCPauseMillis=20

错误表现:

  • G1 尝试做“小步快跑”,每次只回收很少区域

  • GC 调度频繁,导致 GC 开销过大

  • 实际吞吐率反而降低,甚至 Full GC 也变得更频繁

原因分析:

  • G1 的暂停预测模型被“过度限制”

  • GC 频繁被触发,但每次清理量不够,长远看反而回收不及时

优化建议:

  • 放宽 MaxGCPauseMillis 至 100ms 以内,兼顾吞吐和暂停

  • 观察日志中的 “pause target met” 状态,动态调整

例 4:Mixed GC 没有完成,导致 Full GC 重复触发

背景:

  • 应用负载波动大,Mixed GC 经常被中断

  • 老年代利用率飙升,最终触发多次 Full GC

错误表现:

  • G1 执行并发标记后启动 Mixed GC

  • 但在处理 Eden 区时就被打断,老年代未清理

  • 多轮循环 GC 后,内存碎片堆积 ➜ Full GC

原因分析:

  • Mixed GC 本质上是 “老年代的增量回收”

  • 如果每轮 GC 清理量太少,无法及时清理老年代,会累积到需要 Full GC

优化建议:

-XX:G1HeapWastePercent=5         # 允许适度“浪费”,让更多 Region 被选中
-XX:G1MixedGCLiveThresholdPercent=85  # 调整可回收区域的判定阈值
-XX:G1OldCSetRegionThresholdPercent=20  # 每轮最大处理老年代比例

深入理解 JVM 的垃圾收集器:CMS、G1、ZGC | 二哥的Java进阶之路

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/84999.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/84999.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

runas命令让其他用户以管理员权限运行程序

RUNAS 用法: RUNAS使用示例&#xff1a; runas /noprofile /user:mymachine\administrator cmd #本机Administrator管理员身份执行CMD&#xff0c;/noprofile为不加载该用户的配置信息。runas /profile /env /user:mydomain\admin “mmc %windir%\system32\dsa.msc” #本机上…

实战指南:部署MinerU多模态文档解析API与Dify深度集成(实现解析PDF/JPG/PNG)

MinerU web api部署 MinerU 能够将包含图片、公式、表格等元素的多模态 PDF、PPT、DOCX 等文档转化为易于分析的 Markdown 格式。 克隆 MinerU 的仓库 git clone https://github.com/opendatalab/MinerU.gitcd 到 projects/web-api cd projects/web-api在可以科学上网的情况下…

向量外积与秩1矩阵的关系

向量外积与秩1矩阵的关系 flyfish 向量外积是构造秩1矩阵的基本工具&#xff0c;其本质是用两组向量的线性组合刻画矩阵的行和列相关性&#xff1b;任意秩1矩阵必可表示为外积&#xff0c;而低秩矩阵&#xff08;秩 k k k&#xff09;可分解为 k k k 个外积矩阵的和&#x…

设计模式-创建型模式(详解)

创建型模式 单例模式 一个类只允许创建一个对象&#xff0c;称为单例。 单例体现&#xff1a;配置类、连接池、全局计数器、id生成器、日志对象。 懒汉式 (线程不安全) 单例&#xff1a;【不可用】 用到该单例对象的时候再创建。但存在很大问题&#xff0c;单线程下这段代…

什么是BI?有哪些应用场景

BI&#xff08;Business Intelligence&#xff0c;商业智能&#xff09;是通过技术手段对海量业务数据进行采集、整合、分析和可视化的过程&#xff0c;旨在帮助企业从数据中获取洞察&#xff0c;支持决策。其核心是通过工具&#xff08;如Quick BI&#xff09;将原始数据转化为…

从零开始:使用Vite和Vue.js搭建一个空项目

进入node.js官网 https://nodejs.org/zh-cn 下载node.js 点击进行安装&#xff0c; 完成之后&#xff0c;按住shift鼠标右键&#xff0c;打开powershell窗口 输入node -v &#xff0c;出现版本号就是成功了 node -v 接下来&#xff0c;打开设置&#xff0c;搜索开发者设置&…

Redis 核心数据类型及典型使用场景详解

在日常开发中&#xff0c;Redis 不仅是缓存利器&#xff0c;更是一套高性能的数据结构服务。你是否真的了解 Redis 提供的五种核心数据类型&#xff1f;它们各自的底层结构和适用场景又有哪些差异&#xff1f;本篇博客将深入解析 Redis 的数据类型及其典型应用&#xff0c;助你…

threejs webVR获取相机正前方向量

通常获取相机正前方可以使用camera.getWorldDirection(new Vector3()) 函数来得到&#xff0c;但是在threejs0.139.2版本中进入VR后使用上面函数获取的数据是固定不变的&#xff0c;不管是否旋转了头盔&#xff0c;经过一番研究发现必须使用renderer.xr.getCamera() 此函数获取…

华为OD-2024年E卷-字符统计及重排[100分] -- python

问题描述&#xff1a; 给出一个仅包含字母的字符串&#xff0c;不包含空格&#xff0c;统计字符串中各个字母(区分大小写)出现的次数&#xff0c;并按照字母出现次数从大到小的顺序输出各个字母及其出现次数。如果次数相同&#xff0c;按照自然顺序进行排序&#xff0c;且小写…

MCP(模型上下文协议)协议和Http协议对比

MCP&#xff08;Model Context Protocol&#xff0c;模型上下文协议&#xff09;和 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本传输协议&#xff09;是两种定位完全不同的协议&#xff0c;主要区别如下&#xff1a; 1. 核心定位 HTTP 通用网络通信协议&am…

C++打印乘法口诀表

int main()​​&#xff1a; 这是C 程序的入口点。每个C 程序都必须有一个 main 函数&#xff0c;程序从这里开始执行。 ​​外层 for 循环​​&#xff1a; for (int i 1; i < 10; i) { int i 1&#xff1a;定义并初始化循环变量 i 为 1。这里的 i 代表乘法表中的行…

RoGBAG 与 MCAP

RoGBAG 和 MCAP 都是机器人领域常用的二进制数据格式&#xff0c;用于存储传感器数据、控制命令和状态信息。两者主要区别在于&#xff1a; RoGBAG&#xff1a;ROS 1/2 的标准日志格式&#xff0c;采用 LZF/LZ4 压缩&#xff0c;适合中小型数据集 MCAP&#xff1a;新一代机器人…

Ubuntu 空间占用情况排查常用命令

查看当前目录总大小及子目录占用详情 du -sh * | sort -hr ​​du​​&#xff1a;磁盘使用统计命令​​-s​​&#xff1a;显示每个参数的总计&#xff08;不递归子目录&#xff09;​​-h​​&#xff1a;以人类可读格式&#xff08;KB/MB/GB&#xff09;显示​​*​​&…

C语言编译优化实战与技巧

一.概述 1.C语言编译优化介绍 C语言编译优化是提升程序性能的核心手段&#xff0c;涉及从源代码到机器码的多层次转换&#xff0c;下面从优化级别、常用技术、内存管理、指令调度等多个维度详细介绍。 2.编译器优化等级&#xff08;GCC/Clang&#xff09; 二.常用优化技术 1…

Seq2Seq理解

Seq2Seq理解 写在前面&#xff1a;学习Seq2Seq由于前面底子没打好导致理解起来非常困难&#xff0c;今天索性全部搞懂逻辑部分&#xff0c;后续我会把所学的一些算法全部以理解代码的形式发布出来&#xff0c;课程代码内容全部来自李沐老师的视频&#xff0c;再次感谢&#xf…

旅游规划智能体之ReAct Agent实战

引言 本文将系统性地介绍如何运用ReAct框架构建旅游规划智能体&#xff0c;通过LangChain的create_react_agent方法实现智能决策和多步骤任务处理。ReAct框架作为现代AI Agent开发的核心技术之一&#xff0c;为构建具备复杂推理能力的智能系统提供了重要的理论基础和实践指导。…

组合模式深度解析:Java设计模式实战指南与树形结构处理架构设计

组合模式深度解析&#xff1a;Java设计模式实战指南与树形结构处理架构设计 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用…

PHP设计模式实战:领域驱动设计与六边形架构

在前三篇关于电子商务系统、API服务和微服务架构的基础上,我们将深入探讨如何运用领域驱动设计(DDD)和六边形架构(Hexagonal Architecture)构建更加清晰、可维护的业务系统。随着业务复杂度增加,传统的分层架构往往难以清晰地表达业务逻辑,而DDD提供了一套方法论来解决这一问…

为什么在1080p的屏幕下,通常观看4K视频要比1080p的视频来的清晰?

一、分辨率与像素密度的底层逻辑 4K与1080p的像素差异 4K分辨率通常为38402160&#xff08;约830万像素&#xff09;&#xff0c;而1080p为19201080&#xff08;约207万像素&#xff09;&#xff0c;4K像素数量是1080p的4倍。当4K视频在1080p屏幕上播放时&#xff0c;需要将4倍…

C++ Json-Rpc框架 项目逻辑设计

Server • RpcServer&#xff1a;rpc功能模块与⽹络通信部分结合 RpcServer分为两部分 1.提供函数调用服务的服务端 2.提供服务注册的客户端 对内提供好rpc服务的路由关系管理map<method,服务描述对象>&#xff0c;以及rpc请求消息的分发处理函数。给Dispatcher提供onRpc…