news 2026/8/9 7:37:31

JVM 之 垃圾回收算法及其内部实现原理【垃圾回收的核心问题有哪些?分别怎么解决的?可达性分析解决了什么问题?回收算法有哪些?内部怎么实现的?】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM 之 垃圾回收算法及其内部实现原理【垃圾回收的核心问题有哪些?分别怎么解决的?可达性分析解决了什么问题?回收算法有哪些?内部怎么实现的?】

垃圾回收的核心问题如下

  • 1、哪些内存需要回收?
  • 2、什么时候回收?
  • 3、如何回收?

一、如何知道哪些内存需要回收?

引用计数算法 (了解即可)

在对象中添加一个引用计数器,引用一次就+1,引用失效就-1

优势劣势

  • 优势:简单,高效
  • 劣势:需要占用一些额外的空间,循环引用问题难以解决。

可达性分析算法 (目前市面上垃圾收集器都是用的这个)

通过GC Roots的引用链判断对象是否还在使用,

引用链:从GC Roots的根节点集开始,根据引用关系向下搜索所走过的路径。

  • 优势:适用复杂场景,无循环依赖的问题
  • 劣势:遍历成本高,需要STW

可达性分析算法虽有 STW 开销,但能解决循环引用、适配多引用类型,成为 JVM GC 的标准选择,现代 GC 的优化核心也围绕 “减少可达性分析的 STW 时间” 展开

如下图,object5、6、7虽互有关联,但它们到GC Roots是不可达的,所以是可回收的对象。

解决了什么问题

复杂场景中,解决循环依赖问题,准确判定对象是否仍存活的问题

哪些对象是GC Root对象?

  • 1、正在使用中的对象:
    • 1.1、线程正在使用的对象:即虚拟机栈栈帧中的本地变量表所引用的对象。
    • 1.2、JNI正在使用的对象:即在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
    • 1.3、锁正在使用的对象:所有被同步锁(synchronized关键字)持有的对象。
  • 2、类引用的对象
    • 2.1、类的静态变量对象:即方法区中类静态属性引用的对象。
    • 2.2、类的常量对象:即方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  • 3、常驻对象:
    • 3.1、常驻对象:如系统类加载器、基本类型对应的Class对象、一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等。
    • 3.2、jvm管理对象:反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

可达对象一定不回收吗?

这里就要聊引用了
为了适用复杂场景,java引入了强、软、弱、虚四种引用级别。

以解决-》某些可留可不留的对象,在内存紧张(回收)或宽松时(不回收)的存留问题。

不同引用在垃圾回收时的行为

  • 强引用(Strongly Re-ference):不回收(类似“Object obj=new Object()”这种引用关系。)
  • 软引用(Soft Reference):如果第一次回收后还没有足够空间,则第二次时回收。(SoftReference)
  • 弱引用(Weak Reference):GC一定回收(WeakReference类)
  • 虚引用(Phantom Reference)4种:无法通过该引用找到对象,该引用的作用是回收时有系统通知。(PhantomReference类)

不可达的对象一定回收吗?(扩展知识,不建议生产中使用)

不一定的,为啥呢?下述是回收的流程

  • 1、在可达性分析后,不可达的对象会被第一次标记,然后初筛(是否执行过finalize()方法)
  • 执行过finalize(),则回收,
  • 没执行过finalize(),则放入一个名为F-Queue的队列中,然后启动一条低优先级的线程去执行其中对象的finalize()方法(此时如在方法中拯救这个对象,这个对象就不会被回收)

F-Queue的队列中对象的finalize()不保证一定执行;
避免由某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,导致的F-Queue队列中其他对象永久等待,甚至整个内存回收子系统崩溃

方法区回收

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载器的场景中,
通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。

方法会回收触发

  • 核心触发条件:「类的所有实例被回收 + 加载该类的类加载器被回收 + 类对象无引用」
  • Full GC时回收:除 ZGC/Shenandoah 外,均需触发 Full GC 才能回收方法区(Full GC导致的长时间STW,是调优的核心痛点)
  • 元空间如何减少STW:元空间在堆中,可调高full GC触发条件(-XX:MetaspaceSize)

二、垃圾回收算法(何时回收?如何回收?)

垃圾回收算法的核心问题-》平衡如何快速回收+回收后如何高效分配内存

分代收集理论

分代收集理论是后续“标记-清除” “标记-复制” “标记-整理”算法的基础。

分代收集理论建立在三条分代假说之上

  • 1、弱分代假说:绝大多数对象都是朝生夕灭的
  • 2、强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
  • 3、跨代引用假说:跨代引用相对于同代引用来说仅占极少数

分代收集理论解决了什么问题?

如何在垃圾回收时,使垃圾回收更高效(朝生夕灭的对象频繁扫描,难以消亡的不频繁扫描,扫描时区域足够小)

怎么解决的?

  • 根据1、2条原则,划分区域(新生代,老年代)
  • 对象出生在新生代(根据假说1频繁垃圾回收),多次Minor GC仍存活则进入老年代(根据假说2不频繁垃圾回收)。

既然对象划分了区域,那肯定会有跨带引用的问题,如果年轻代回收时因为跨代引用,所以可达性分析时扫描整个年老代,显然时不划算的。

  • 所以根据假说3,在新生代中维护“记忆集”,Minor GC时,只扫描记忆集中包含跨代引用的小块内存。

记忆集:把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用

标记-清除算法(老年代的 “并发低延迟回收”,主打快)

标记-清除算法是后续的收集算法的基础
他有“标记”和“清除”两个阶段:标记阶段判断哪些对象需要回收,回收阶段回收这些对象。

该算法存在的问题

  • 1、执行效率不稳定:对象数量大时,回收效率低
  • 2、回收后内存碎片化:回收后会产生大量碎片化内存,可能导致后续大对象找不到足够的连续内存,进而导致不得不再次GC。

仅 CMS GC 的老年代回收核心用标记 - 清除;
触发 “内存碎片过多” 时,会降级为标记 - 整理(CMS Full GC),或触发 Full GC。

什么时候触发GC(Major GC)

  • 核心条件:老年代内存使用率达到预设阈值(如 CMS GC 的-XX:CMSInitiatingOccupancyFraction,默认 92%);
  • 触发频率:低(秒 / 分钟级),仅当老年代内存接近阈值时触发。

标记-复制算法(新生代的 “高频轻量回收”)

也叫复制算法。基于“半区复制”算法。

  • 行为: 将内存划分为两块,每次只使用其中一块,当快使用完时,就将标记存活对象复制到另一块,然后整个回收这一块。
  • 优势:分配对象时快速高效,只需顺序移动磁头
  • 劣势:可用内存缩小到了原来的一半。空间浪费多

Appel式回收(一般用的都是这个,解决空间浪费问题)

基于标记-复制算法,优势是对内存的利用率更高了,且保留了标记-复制算法的高效的对象分配行为。

实现:基于新生代中98%的对象熬不过第一次收集的特点,
将新生代划分为Eden : from Survivor(S0) : to Survivor(S1) = 8∶1:1(默认,可调整)

什么时候触发GC(Minor GC)

  • 核心条件:Eden区内存被占满,新对象无法分配内存(最主要)
  • 触发频率:极高(毫秒 / 秒级),因为新生代对象 “朝生夕死”,Eden 区会快速被占满。

Appel式回收的行为

  • 1、分配对象:
    • 1.1、新创建的对象分配到Eden区(大对象除外)
    • 1.2、Eden区满时触发Minor GC
  • 2、执行Minor GC
    • 2.1、标记Eden + S0中存活对象(可达性分析);
    • 2.2、存活对象复制到S1中(避免碎片);
    • 2.3、清空 Eden + S0
    • 2.4、角色互换:S0 和 S1 的标识交换(原 S1 变为新的 S0,原 S0 变为新的 S1),下一次 GC 时新的 S1 作为空闲目标区。
  • 3、对象晋升老年代
    • 3.1、GC年龄到了(即对象在Survivor区经历15次(默认-XX:MaxTenuringThreshold=15)Minor GC仍存活)
    • 3.2、S1空间不足:复制到 S1 的对象总大小超过 S1 容量,部分对象直接晋升老年代;
    • 3.4、大对象:超过阈值的对象(-XX:PretenureSizeThreshold)直接分配到老年代。

标记-整理算法(老年代的 “全量/碎片修复回收”,解决内存碎片问题)

老年没有额外空间进行担保,所以没办法使用Appel回收。又不想浪费50%空间,所以 标记-复制 算法也pass。
最终,针对老年代对象的特点, 标记-整理算法 应运而生。

行为:标记过后,让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存

标记-整理算法的优劣势

  • 优势:整理操作,可以拥有连续的磁盘空间,进而使用指针碰撞的方式分配内存,带来更大的吞吐量
  • 劣势:基于老年代对象多数继续存活的特点,整理时移动对象需要STW(Stop The World),这是一种极为负重的操作,势必带来更大的延迟.

总结:
关注吞吐量就用 标记-整理 算法(例如基于标记-整理算法的Parallel Scavenge收集器)
关注延迟就用标记-清除算法(例如基于标记-清除算法的CMS收集器)

什么时候触发GC(Major GC)

  • 核心条件
    • 1、老年代内存满,或 Minor GC 后晋升的对象无法在老年代分配连续空间(碎片导致);
    • 2、CMS GC 标记 - 清除后碎片过多,触发 “CMS Full GC”;
    • 3、手动调用System.gc()(JVM 可能触发标记 - 整理);
    • 4、G1/ZGC 回收老年代 Region 时(需压缩内存);
    • 5、元空间不足触发 Full GC 时,老年代同步执行标记 - 整理;
  • 触发频率:极低(分钟 / 小时级),仅当标记 - 清除无法解决问题时触发

垃圾回收算法实现细节

根节点枚举(Root Enumeration)

  • 核心作用:可达性分析的起点,快速找到所有GC Roots
  • 核心行为:先STW(类似生成快照,保障不变化),然后获取GC Roots 引用关系;
  • HotSpot优化:将GC Roots存储在OopMap中,枚举时直接遍历OopMap,无需扫描整个栈/方法区,大幅缩短枚举时间

OopMap 在 类加载/即时编译时生成,记录栈帧/寄存器中引用对象的位置

  • 局限性:如果GC Roots跨线程栈、方法区,单纯STW无法保证各线程不会改变引用关系,所以枚举时就需STW+安全点+安全区域保证枚举时引用关系稳定。

安全点(Safepoint)

  • 核心作用:GC时,保障运行中的线程暂停或恢复时,处于统一节点 (此时对象引用关系不会变化,避免可达性分析出错)
  • 核心行为
    • 安全点的选择:仅在 “指令序列的特定位置”设置,避免过于密集导致性能损耗(如方法返回、循环结束、异常抛出、调用方法后);
    • 线程暂停机制:
      • 主动式暂停:JVM 触发 GC 时,设置 “安全点标志”,用户线程执行到安全点时检查标志,若为 GC 状态则主动暂停;
      • 被动式暂停(少数):通过信号量强制线程停到最近的安全点。
  • 局限性:如果线程处于休眠/阻塞状态(如 sleep、wait),就会无法走到安全点,所以需安全区域进行补充。

安全区域(Safe Region)

  • 核心作用:把非运行(如sleep 、wait、park)的线程限制住,避免这些线程在GC时恢复运行,进而修改引用关系。
  • 核心行为
    • GC忽略处理进入安全区域的线程
    • 安全区域内的线程在GC结束前不会恢复运行

记忆集(Remembered Set)与卡表(Card Table)

记忆集(抽象概念):记录 “从非收集区(如老年代)指向收集区(如新生代)” 的引用,本质是 “指向指针的指针集合”;
卡表(记忆集的具体实现):将老年代内存划分为大小固定的 “卡页”(默认 512 字节),卡表是一个字节数组,每个元素对应一个卡页,值为 0(干净)或 1(脏);

  • 核心作用:解决跨代引用问题,Minor GC时仅扫描有跨代引用的老年代区域,避免扫描整个老年代。
  • 核心行为
    • 标记:当老年代对象引用新生代对象时,对应的卡页标记为 “脏”(1);
    • Minor GC时行为:仅扫描卡表中 “脏” 的卡页,找到跨代引用,作为 GC Roots 的补充,无需遍历整个老年代。

写屏障(Write Barrier)

  • 核心作用:拦截“对象引用更新”操作,维护卡表的“脏标记”;同时支持并发可达性分析的“增量更新/原始快照”。
  • 核心行为
    • 卡表写(最常用):
      • 前置写屏障:更新引用前,标记对应卡页为脏;
      • 后置写屏障:更新引用后,标记对应卡页为脏(HotSpot 采用);
        示例:当执行obj.ref = newObj(老年代 obj 引用新生代 newObj),写屏障会触发,将 obj 所在卡页标记为脏;
    • 并发GC:
      • 增量更新(Incremental Update):保证并发GC时,新增的引用能被扫描到;
      • 原始快照(SATB, Snapshot At The Beginning):保证并发GC时,删除的引用仍能被扫描到,避免漏判存活对象。
  • 代价:每次引用更新都要执行写屏障指令,带来微小的 CPU 开销,但远低于全堆扫描的成本。

并发的可达性分析(Concurrent Reachability Analysis)

  • 核心作用:将可达性分析的大部分工作放到 “用户线程运行时” 执行,仅在初始标记、重新标记阶段短暂 STW,大幅减少 STW 时间(如 CMS/G1/ZGC 的核心优化)。
  • 核心行为: 分阶段执行
    • 初始标记(STW):枚举 GC Roots,标记直接关联的对象(耗时极短);
    • 并发标记(无 STW):从初始标记的对象出发,遍历整个引用链,标记存活对象(此时用户线程仍在运行,引用关系可能变化);
    • 重新标记(短 STW):修正并发标记期间因用户线程操作导致的标记错误(依赖写屏障记录的引用变化);
  • 关键依赖:写屏障记录并发标记期间的引用更新,安全点+安全区域保证重新标记时线程暂停,卡表减少跨代引用的扫描范围。

核心协作流程(以 G1 GC的Minor GC为例)

  • 1、触发 GC,线程暂停准备
    • 1.1、JVM 检测到 Eden 区满,触发 Minor GC;
    • 1.2、JVM 设置 “安全点标志”,通知所有运行中的用户线程:执行到最近的安全点时暂停;
    • 1.3、对于处于休眠 / 阻塞的线程:因已进入安全区域,无需主动暂停,JVM 标记这些线程为 “无需处理”;
    • 1.4、所有用户线程停在安全点 / 安全区域后,进入短暂 STW。
  • 2、根节点枚举(STW)
    • 2.1、JVM 遍历「OopMap」快速枚举核心 GC Roots(虚拟机栈、静态变量、JNI 引用);
    • 2.2、同时扫描卡表中标记为 “脏” 的老年代卡页,提取其中的跨代引用(老年代→新生代),作为 GC Roots 的补充;
    • 2.3、仅枚举 GC Roots 和脏卡页的跨代引用,无需扫描整个老年代(卡表的核心价值)。
  • 3、并发可达性分析(部分收集器如 G1/ZGC 支持)
    • 3.1、若为并发 GC(如 G1 的混合收集),初始标记(枚举 GC Roots)后,释放 STW,进入并发标记阶段;
    • 3.2、用户线程恢复运行,JVM 同时遍历引用链标记存活对象;
    • 3.3、写屏障拦截所有引用更新操作:
      • 3.3.1、若为跨代引用(老年代→新生代),自动标记对应卡页为 “脏”(维护卡表);
      • 3.3.2、若为普通引用更新,记录到 “修改引用日志”(供重新标记阶段修正)。
  • 4、重新标记(短 STW)
    • 4.1、JVM 再次触发安全点,暂停所有用户线程(安全区域线程仍无需处理);
    • 4.2、基于写屏障记录的 “修改引用日志”,修正并发标记期间的标记错误(如新增引用未标记、删除引用误标记);
    • 4.3、再次检查卡表的脏页,补充跨代引用的标记结果。
  • 5、完成标记,执行垃圾回收
    • 5.1、可达性分析完成,确定存活对象;
    • 5.2、执行复制算法:将 Eden+From Survivor 的存活对象复制到 To Survivor,晋升老年代的对象直接复制到老年代;
    • 5.3、清空 Eden 和 From Survivor,S0/S1 角色互换;
    • 5.4、JVM 清除安全点标志,唤醒所有用户线程(安全区域的线程检查 GC 完成后,也恢复运行);
    • 5.5、写屏障继续工作,监控后续的引用更新,为下一次 GC 维护卡表。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 4:47:38

LyricsX桌面歌词工具:重新定义音乐沉浸体验

LyricsX桌面歌词工具:重新定义音乐沉浸体验 【免费下载链接】Lyrics Swift-based iTunes plug-in to display lyrics on the desktop. 项目地址: https://gitcode.com/gh_mirrors/lyr/Lyrics 你是否曾经在聆听心爱歌曲时,不得不频繁在音乐播放器和…

作者头像 李华
网站建设 2026/8/8 19:23:32

ZonyLrcToolsX:终极音乐歌词管理解决方案完整指南 [特殊字符]

ZonyLrcToolsX:终极音乐歌词管理解决方案完整指南 🎵 【免费下载链接】ZonyLrcToolsX ZonyLrcToolsX 是一个能够方便地下载歌词的小软件。 项目地址: https://gitcode.com/gh_mirrors/zo/ZonyLrcToolsX 在数字音乐时代,歌词是完整音乐…

作者头像 李华
网站建设 2026/8/9 2:49:55

近红外光谱数据集实战手册:5大核心应用场景深度解析

近红外光谱数据集实战手册:5大核心应用场景深度解析 【免费下载链接】Open-Nirs-Datasets Open source data set for quantitative and qualitative analysis of near-infrared spectroscopy 项目地址: https://gitcode.com/gh_mirrors/op/Open-Nirs-Datasets …

作者头像 李华
网站建设 2026/8/9 5:49:36

终极指南:5分钟快速部署智能API网关,统一管理所有AI模型

终极指南:5分钟快速部署智能API网关,统一管理所有AI模型 【免费下载链接】new-api 基于One API的二次开发版本,仅供学习使用! 项目地址: https://gitcode.com/gh_mirrors/ne/new-api 在AI应用开发爆炸式增长的今天&#xf…

作者头像 李华
网站建设 2026/8/9 4:20:04

GHelper完全攻略:华硕笔记本硬件控制与性能优化终极指南

GHelper完全攻略:华硕笔记本硬件控制与性能优化终极指南 【免费下载链接】g-helper Lightweight Armoury Crate alternative for Asus laptops. Control tool for ROG Zephyrus G14, G15, G16, M16, Flow X13, Flow X16, TUF, Strix, Scar and other models 项目地…

作者头像 李华
网站建设 2026/8/7 12:14:41

B站视频下载神器:BiliDownloader全方位使用指南

还在为无法离线观看B站优质内容而烦恼吗?今天为大家介绍一款界面精简、操作简单且高速下载的B站视频下载工具——BiliDownloader!无论你是想收藏UP主的精彩作品,还是需要离线学习教学视频,这款神器都能帮你轻松搞定! 【…

作者头像 李华