当前位置: 首页 > news >正文

垃圾回收算法有哪些区别,复制与标记整理怎么选

对象存活判定:为什么引用计数法被淘汰

在深入垃圾回收算法之前,我们首先得解决一个根本问题:JVM 如何判断一个对象是“垃圾”还是“活物”?只有准确识别出死亡对象,后续的清理工作才有意义。

早期的一些语言或特定场景下,曾使用过引用计数法。它的逻辑非常直观:给每个对象维护一个计数器,每当有一个地方引用它,计数器就加 1;当引用失效时,计数器就减 1。任何时刻计数器为 0 的对象,理论上就是不可能再被使用的,可以回收。

这种方法实现简单,判定效率也高,但它有一个致命的缺陷:无法解决对象之间相互循环引用的问题

想象这样一个场景:对象 A 有一个字段指向对象 B,而对象 B 也有一个字段指向对象 A。除此之外,没有任何其他对象引用它们。此时,虽然 A 和 B 实际上已经不可达,应该被回收,但因为它们互相引用,各自的计数器都不为 0。JVM 会误以为它们还在被使用,从而导致内存泄漏。

publicclassReferenceCountingDemo{// 定义一个成员变量用于互相引用Objectinstance=null;publicstaticvoidmain(String[]args){ReferenceCountingDemoobjA=newReferenceCountingDemo();ReferenceCountingDemoobjB=newReferenceCountingDemo();// 互相引用objA.instance=objB;objB.instance=objA;// 断开外部引用objA=null;objB=null;// 此时 objA 和 objB 的引用计数都不为 0// 如果使用引用计数法,这两个对象将无法被回收}}

正因为这个无法回避的缺陷,主流的 Java 虚拟机(如 HotSpot)并没有采用引用计数法,而是选择了更严谨的可达性分析算法(Reachability Analysis)。

可达性分析的核心思想是从一组称为"GC Roots"的对象开始向下搜索。如果一个对象到 GC Roots 没有任何引用链相连,则证明该对象是不可用的。在 Java 中,可作为 GC Roots 的对象主要包括:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。

通过这种“顺藤摸瓜”的方式,所有未被标记的对象都会被判定为垃圾,从而彻底解决了循环引用的难题。

四大核心回收算法详解

确定了哪些对象是垃圾后,下一步就是如何高效地清理它们。JVM 演化出了四种经典的垃圾回收算法,每种都有其独特的适用场景和优缺点。

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

这是最基础也是最直观的算法,分为两个阶段:

  1. 标记:从 GC Roots 开始,标记所有需要回收的对象。
  2. 清除:统一回收所有被标记的对象。

它的优点是实现简单,但缺点同样明显:效率不高(标记和清除两个过程都需要遍历),且会产生大量不连续的内存碎片。当后续需要分配较大对象时,可能因为找不到足够的连续内存空间而不得不提前触发另一次垃圾收集动作。

复制算法(Copying)

为了解决内存碎片问题,复制算法应运而生。它将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块用完了,就将还存活的对象复制到另外一块上,然后把已使用过的内存空间一次清理掉。

这种算法的优点非常明显:

  • 高效:没有标记和清除过程,实现简单,运行高效。
  • 无碎片:整理后的内存是连续的,分配新对象时只需移动指针即可。

但它的代价是内存利用率低,正常使用时只能利用一半的内存,这在内存宝贵的年代是难以接受的。不过,随着硬件发展,这种牺牲空间换时间的策略在特定区域变得非常可行。

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

针对老年代对象存活率高、不适合复制的特点,标记 - 整理算法被提出。它的标记过程与“标记 - 清除”一样,但后续步骤不是直接清除,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

这样既避免了内存碎片的产生,又不像复制算法那样需要两倍的内存空间。当然,移动对象涉及大量的指针更新和对象搬移,时间成本相对较高

分代收集算法(Generational Collection)

现代 JVM 并不单一地使用上述某种算法,而是根据对象存活周期的不同,将堆内存划分为新生代老年代,针对不同年代采用最适合的算法,这就是分代收集理论。

  • 新生代:对象“朝生夕死”,存活率极低。因此适合使用复制算法,只需付出少量存活对象的复制成本,就能高效清理大部分垃圾。
  • 老年代:对象存活率高,没有额外担保机制。因此适合使用标记 - 清除标记 - 整理算法。

新生代为何偏爱复制算法

在 HotSpot 虚拟机中,新生代的设计完美诠释了复制算法的优势。虽然理论上复制算法需要 1:1 的空间比例,但在实际的新生代应用中,并不需要这么奢侈。

新生代被细分为三个区域:Eden 区和两个大小相等的Survivor 区(From 和 To)。默认情况下,Eden 区与 Survivor 区的比例是8:1:1。这意味着,JVM 只需要预留 10% 的内存作为空闲缓冲,就能应对绝大多数情况。

对象流转的过程如下:

  1. 新创建的对象优先在Eden 区分配。
  2. 当 Eden 区满时,触发Minor GC
  3. GC 发生时,将 Eden 区和当前 From Survivor 区中存活的对象,一次性复制到 To Survivor 区。
  4. 清理掉 Eden 区和 From Survivor 区。
  5. 交换 From 和 To 的角色,保持 From 区始终为空,等待下一次 GC。

在这个过程中,还有一个关键机制:对象年龄增长
每次对象在 Survivor 区之间复制存活下来,其“年龄”就加 1。当年龄达到一定阈值(默认为 15)时,对象就会被晋升到老年代。这个阈值可以通过-XX:MaxTenuringThreshold参数调整。

此外,还有一种特殊情况:大对象直接进入老年代
如果在 Eden 区分配一个大对象(如长字符串或大型数组),导致无法放入,或者即使放入也会在下次 GC 时因 Survivor 区装不下而失败,JVM 会策略性地直接将其分配到老年代。这避免了在大对象身上进行频繁的复制操作,提升效率。我们可以通过-XX:PretenureSizeThreshold参数来设定这个大对象的阈值(该参数仅对 Serial 和 ParNew 收集器有效)。

这种设计巧妙地利用了“大部分对象朝生夕死”的特性,用极小的内存代价换取了极高的回收效率。

老年代与内存碎片的博弈

如果说新生代是“快进快出”的游乐场,那么老年代就是“长期居住”的社区。这里的对象存活率极高,如果使用复制算法,不仅浪费空间,而且每次 GC 都要搬运大量数据,效率极低。因此,老年代通常采用标记 - 清除标记 - 整理算法。

这里就引出了一个核心矛盾:内存碎片

如果使用标记 - 清除算法,回收后会留下大量不连续的内存空洞。当程序需要分配一个稍大的对象时,可能剩余总内存足够,但没有一块连续的空間容纳它,从而被迫触发 Full GC 甚至抛出OutOfMemoryError

为了解决这个问题,标记 - 整理算法成为了老年代的主流选择(如 CMS 收集器的备选方案 Serial Old,以及 G1 的部分场景)。它在回收前先进行一次“整理”,将存活对象紧凑地排列在一端。虽然这个过程比单纯的清除要慢,因为它涉及对象的移动和指针修正,但它保证了内存的连续性,避免了碎片化带来的分配失败风险。

在实际生产中,选择哪种策略往往取决于应用对停顿时间(STW)和吞吐量的敏感度:

  • 如果应用对响应时间极其敏感,不能容忍长时间的停顿,可能会倾向于使用并发标记清除(如 CMS),哪怕忍受一定的碎片风险(通过参数-XX:+UseCMSCompactAtFullCollection在 Full GC 时进行整理)。
  • 如果应用更看重整体吞吐量,且能接受稍长的 GC 停顿,那么标记 - 整理算法(如 Parallel Old)通常是更稳妥的选择,因为它能保证内存分配的稳定性。
http://www.rkmt.cn/news/1448120.html

相关文章:

  • 2026年进出口报关公司哪家好?行业服务能力深度解析 - 品牌排行榜
  • 微信3大自动回复,解放双手还能提升成交率
  • 2026广州翡翠回收全攻略:种水色工+避坑指南,合扬专业鉴定夺魁 - 合扬奢侈品交易中心
  • 3个月攻克408考研:我的高效学习笔记系统完整指南
  • 成都黄金回收性价比门店大比拼 2026|全城筛选,合扬脱颖而出 - 合扬奢侈品交易中心
  • 【AI+监控系统黄金组合】:Gartner 2024验证的3层架构模型首次公开
  • 云端教育工具赋能气候变化教学:从数据探究到科学思维培养
  • 2026年访客系统大揭秘:哪家技术强且性价比高?快来一探究竟! - 智能硬件-产品评测
  • 如何高效使用TMSpeech:Windows本地实时语音转文字完整指南
  • 赋能心理咨询师OPC创业,拾棠榛果心理测试系统,打造单人执业新范式 - 资讯焦点
  • 洛阳空调维修市场的水有多深?一家开了多年的本地维修部说出了真相 - 速递信息
  • 我发现一个发财的机会--------只要发现几个android漏洞奖励几十万美元
  • 从零搭建手势控制Stewart平台:Arduino实现并联机器人运动学
  • 告别手写代码!用Playwright CLI录制脚本,5分钟搞定自动化测试入门
  • LED净化平板灯推荐怎么选?医院/无尘车间专用避坑指南(2026年6月最新) - 商业新知
  • 特征血缘断裂正在摧毁你的AI可信度(附Gartner 2024验证:仅17%企业具备端到端AI特征可追溯能力)
  • ESP32物联网设备固件本地编译与定制:从Tasmota源码到硬件刷写全流程
  • Arduino可穿戴灯光雕塑:从流水灯到温度交互的创意实现
  • Loop:macOS窗口管理终极解决方案,免费开源提升桌面效率300%
  • 基于Arduino与AMG8833热成像传感器的人体区域检测系统设计与实现
  • 基于Arduino的智能储物盒:从电容触摸传感器到伺服电机控制的完整实现
  • 终极防撤回解决方案:Windows版微信QQ消息永久保存指南
  • 一体化安全协同:从协作工具到企业数字化中枢的演进
  • Ansaldo 0000-9056-01低电平信号开关板
  • 新手必看:用Keil和Proteus 8.9给51单片机做个简易秒表(附完整代码和仿真文件)
  • 为什么这个免费工具能让你的抖音素材收集效率提升3倍:完整实战指南
  • 79万条医患对话:医疗AI变革的黄金燃料
  • 基于Arduino与声音传感器的电脑开机自动化系统设计与实现
  • 基于Arduino与BNO055的推力矢量控制(TVC)系统设计与实现
  • 7-Zip-zstd:6大现代压缩算法如何重塑你的文件处理工作流