在程序设计的世界里,“内存”是绕不开的核心命题。早期程序员需手动管理内存分配与释放,一个疏忽就可能导致内存泄漏或野指针,轻则程序崩溃,重则引发系统故障。垃圾回收(Garbage Collection,简称 GC)技术的出现,彻底改变了这一局面——它让程序自动识别并回收不再使用的内存资源,将开发者从繁琐的内存管理中解放出来。从最初的标记-清除算法到如今的 ZGC、Shenandoah 等低延迟 GC,这项技术的演进始终围绕着“性能”与“效率”的核心诉求,见证了计算领域对更高并发、更低延迟的不懈追求。
一、传统 GC:奠定基础,却困于性能瓶颈
传统 GC 技术的发展始于 20 世纪 60 年代,其核心目标是解决“内存自动回收”的基本问题。这一阶段的技术虽实现了自动化管理,但在并发性能和延迟控制上存在明显短板,难以满足现代高并发应用的需求。
1.1 标记-清除:GC 技术的“开山之作”
标记-清除(Mark-Sweep)算法是最早成熟的 GC 算法,由约翰·麦卡锡于 1960 年提出,至今仍在部分场景中应用。其核心逻辑分为两步:标记阶段遍历所有存活对象并做标记,清除阶段回收未被标记的垃圾对象。
作为 GC 技术的基石,标记-清除算法的优势在于实现简单、无需移动对象。但它的缺陷也十分突出:一是“内存碎片”问题——回收后的内存呈分散状态,当需要分配大对象时,即便总空闲内存足够,也可能因无法找到连续内存块而触发 GC;二是“Stop-The-World(STW)”停顿——标记和清除过程需暂停所有应用线程,内存越大,停顿时间越长,这在早期小型程序中影响不明显,但在大型应用中会直接导致程序响应迟缓。
1.2 标记-复制:用空间换时间的优化
为解决标记-清除的内存碎片问题,标记-复制(Mark-Copy)算法应运而生。它将内存划分为大小相等的两个区域:“From 空间”和“To 空间”。程序仅在 From 空间分配内存,GC 时标记存活对象并复制到 To 空间,随后清空 From 空间,最后交换两个空间的角色。
这种算法的核心优势是彻底消除了内存碎片,且清除过程高效——只需直接清空整个 From 空间。但代价是“空间浪费”:始终有一半内存处于闲置状态,对于内存资源紧张的场景极不友好。此外,复制大量存活对象时仍会产生较长的 STW 停顿,不适用于存活对象较多的老年代内存区域。
1.3 标记-整理:平衡碎片与空间的折中方案
针对标记-清除的碎片问题和标记-复制的空间浪费问题,标记-整理(Mark-Compact)算法实现了折中。它的标记阶段与标记-清除一致,核心差异在后续步骤:清除阶段并非直接回收垃圾对象,而是将所有存活对象向内存一端移动,随后清除边界外的所有垃圾。
标记-整理算法既解决了内存碎片问题,又避免了标记-复制的空间浪费,适用于对象存活率高的老年代。但它的缺陷也很明显:对象移动过程中需要更新所有引用指向,操作复杂且耗时,STW 停顿时间往往比前两种算法更长,在高并发场景下难以接受。
1.4 分代回收:基于对象生命周期的优化范式
随着应用规模扩大,单一 GC 算法已无法兼顾各阶段内存的需求。基于“对象生命周期分化”的规律——大部分对象存活时间短(如局部变量),少数对象存活时间长(如缓存数据),分代回收(Generational GC)成为传统 GC 的主流范式。
以 Java 的 HotSpot 虚拟机为例,分代回收将内存划分为新生代和老年代:新生代采用标记-复制算法,利用对象“朝生夕死”的特点,仅复制少量存活对象即可完成回收,STW 停顿短;老年代采用标记-清除或标记-整理算法,应对存活时间长的对象,减少回收频率。此外,部分虚拟机还引入永久代(或元空间)存储类信息等静态数据。
分代回收在一定程度上优化了 GC 性能,但并未彻底解决 STW 问题。当老年代内存满触发“Full GC”时,仍需对整个堆内存进行扫描和整理,产生的长时间停顿成为高并发应用的“性能杀手”——例如,在金融交易、实时通信等场景中,数百毫秒甚至秒级的停顿可能直接导致业务中断。
二、现代 GC 的破局:以低延迟为核心的技术革命
进入 21 世纪,随着大数据、微服务等技术的兴起,应用对“低延迟”和“大内存”的需求日益迫切。传统 GC 的 STW 停顿问题愈发突出,推动 GC 技术从“优化停顿时间”向“消除长停顿”演进。ZGC(Z Garbage Collector)和 Shenandoah 正是这一革命的代表——它们以“亚毫秒级停顿”为目标,通过创新的算法设计,实现了 GC 与应用线程的高度并发。
2.1 核心突破:从“串行/并行”到“并发”的范式转变
传统 GC 的 STW 问题根源在于“标记、清理、移动”等核心步骤需独占内存资源,无法与应用线程并行执行。而 ZGC 和 Shenandoah 的核心突破在于将 GC 操作与应用线程的冲突降至最低,仅在关键步骤保留极短的 STW 停顿,大部分工作在应用线程运行的同时完成。
实现这一突破的关键技术包括“着色指针”“读屏障”和“并发整理”,这些技术彻底改变了 GC 与应用线程的交互方式,为大内存场景下的低延迟提供了可能。
2.2 ZGC:甲骨文的“毫秒级停顿”利器
ZGC 是 Oracle 为 OpenJDK 开发的新一代 GC,首次发布于 JDK 11,目标是“支持 TB 级内存、GC 停顿不超过 10 毫秒”。它的核心设计围绕“着色指针”和“读屏障”展开,实现了高效的并发标记与整理。
着色指针是 ZGC 的标志性技术。它利用 64 位地址空间的冗余位(如第 42-47 位)存储对象的“标记状态”,无需额外内存空间记录标记信息。当应用线程访问对象时,通过“读屏障”检查指针的着色状态,若处于 GC 标记阶段,自动协助完成标记工作,实现了 GC 与应用线程的协同标记。
在内存整理阶段,ZGC 采用“并发重定位”技术。它先标记需要移动的对象,然后在应用线程访问这些对象时,通过读屏障将指针“原子性更新”到新地址,同时异步完成对象的移动。整个过程中,仅在初始标记和最终标记阶段存在极短的 STW 停顿,其余步骤完全与应用线程并行。
ZGC 的优势不仅在于低延迟,还在于对大内存的高效支持。它采用“区域化内存管理”,将堆内存划分为多个大小可变的区域(Region),可根据内存使用情况动态创建或销毁区域,避免了对整个堆的扫描,大幅提升了大内存场景下的 GC 效率。
2.3 Shenandoah:红帽主导的“无停顿”GC
Shenandoah 由红帽公司主导开发,首次集成于 JDK 12(作为实验性特性),JDK 17 中成为正式特性。它的核心目标与 ZGC 一致——实现低延迟垃圾回收,但在技术实现上采用了不同的路径,核心是“连接指针”和“写屏障”。
Shenandoah 采用“连接指针”(Forwarding Pointer)记录对象的新地址,当对象需要移动时,先在原对象头部设置连接指针指向新地址,然后异步完成对象的复制。应用线程访问对象时,通过“写屏障”检查连接指针,若对象已移动,自动跳转至新地址,避免了指针失效问题。
与 ZGC 相比,Shenandoah 的特点是“全并发整理”——不仅标记过程并发,内存整理的整个过程也无需 STW 停顿。它通过“增量更新”和“最终标记”机制确保标记的准确性,同时利用“并行引用处理”优化引用链的处理效率。此外,Shenandoah 对内存大小的适应性更强,从几百 MB 到 TB 级内存均可高效运行,且对硬件资源的消耗相对较低。
在实际应用中,Shenandoah 更注重“普适性”,能在不同硬件配置和应用场景下保持稳定的低延迟表现,尤其在云原生环境中,其轻量级的设计使其成为微服务应用的理想选择。
三、演进背后的核心逻辑:需求驱动与技术迭代
从传统 GC 到 ZGC/Shenandoah,垃圾回收技术的演进并非孤立的技术创新,而是“应用需求”与“硬件发展”双轮驱动的结果。
一方面,应用场景的升级推动 GC 向低延迟演进。早期程序多为单机、低并发场景,百毫秒级的 STW 停顿可被接受;而如今的金融交易、实时推荐、自动驾驶等场景,对延迟的要求已降至毫秒甚至亚毫秒级,传统 GC 的性能瓶颈成为不可逾越的障碍。ZGC 和 Shenandoah 的出现,正是为了满足这些极端场景的需求。
另一方面,硬件的发展为 GC 技术提供了支撑。64 位处理器的普及让“着色指针”等技术成为可能,大内存硬件(如 TB 级服务器内存)的出现则要求 GC 技术突破对内存规模的限制。同时,多核心 CPU 的发展也为并发 GC 提供了硬件基础——GC 线程与应用线程可在不同核心上并行运行,避免了资源竞争导致的性能损耗。
四、未来展望:GC 技术的下一个方向
随着计算技术的持续发展,GC 技术仍在不断演进。未来,垃圾回收技术可能向以下几个方向突破:
一是“智能化 GC”。通过机器学习算法分析应用的内存使用模式,动态调整 GC 策略(如区域大小、回收时机),实现“按需优化”,进一步降低延迟并提升资源利用率。
二是“分布式 GC”。在分布式系统中,跨节点的内存资源管理成为新的挑战,未来可能出现支持分布式场景的 GC 技术,实现多节点内存的协同回收与优化。
三是“低功耗 GC”。在移动端、物联网设备等场景中,功耗是核心诉求,未来 GC 技术将在低延迟的同时,进一步优化功耗,适应嵌入式设备的需求。
结语
垃圾回收技术的演进史,是一部“解放开发者、优化系统性能”的历史。从标记-清除的简陋实现,到分代回收的精细优化,再到 ZGC、Shenandoah 的低延迟革命,每一次技术突破都源于对“更好性能”的追求。在计算技术日益复杂的今天,GC 技术仍将是核心技术之一,它的发展将持续为高并发、低延迟应用提供坚实的支撑,推动程序设计领域不断向前。

1051

被折叠的 条评论
为什么被折叠?



