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

JVM常见的垃圾回收器

引言

在 Java 虚拟机(JVM)中,垃圾回收(Garbage Collection, GC)是内存管理的核心机制之一,它通过自动回收不再使用的对象来释放内存资源,确保应用程序的高效运行。随着 Java 应用规模的不断扩大和性能需求的日益提高,垃圾回收器的设计和实现也在不断演进,从经典的串行回收器到现代的高效并发回收器,每一种垃圾回收器都有其独特的适用场景和优化目标。

本文将深入探讨 Java 中的经典垃圾回收器及其分类,包括新生代垃圾回收器和老年代垃圾回收器,并重点分析它们的核心算法、性能特点以及适用场景。同时,我们还将介绍现代垃圾回收器的代表——G1(Garbage First)收集器,它通过创新的区域化内存管理和可预测的停顿时间模型,为大规模应用提供了更高效的垃圾回收解决方案。

1. 经典的垃圾回收器

2. 新生代垃圾收集器

Serial:单线程回收垃圾,采用标记-复制算法。

ParNew:多线程并行回收垃圾,已低延迟为目的,采用标记-复制算法。

ParallelScavenge:多线程并行回收垃圾,已吞吐量可控为目的,采用标记-复制算法。

-XX:GCTimeRatio,控制吞吐量大小。

-XX:MaxGCPauseMillis,控制最大垃圾收集停顿时间的。

-XX:+UseAdaptiveSizePolicy,开启后不需要人工指定新生代的大小、Eden与Survivor区的比例以及大对象自动晋升老年代参数。

注意:垃圾收集停顿时间缩短是以牺牲吞吐量和新生代空间为代价换取的:系统把新生代调得小一些,收集300MB新生代肯定比收集500MB快,但这也直接导致垃圾收集发生得更频繁,原来10秒收集一次、每次停顿100毫秒,现在变成5秒收集一次、每次停顿70毫秒。停顿时间的确在下降,但吞吐量也降下来了。

更注重整体系统的工作效率而不是单次垃圾回收的停顿时间,适用于对实时性要求相对较低的应用。

ParNew和ParallelScavenge同样是多线程,会产生STW。ParNew的低延迟是建立在更小的新生代基础上,ParallelScavenge的高吞吐是建立在更大的新生代基础上。

3. 老年代垃圾回收器

Serial Old:单线程回收垃圾,采用标记-整理算法。

Parallel Old:多线程并行回收垃圾,采用标记-整理算法。

CMS:多线程并发回收垃圾,也称“并发低停顿收集器”,采用标记-清理算法。

CMS整个过程分为四个步骤:

1)初始标记(CMS initial mark)

2)并发标记(CMS concurrent mark)

3)重新标记(CMS remark)

4)并发清除(CMS concurrent sweep)

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。

初始标记阶段仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;

并发标记阶段是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行;

重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,基于增量更新;

并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的;

CMS三大缺点:

1. 对处理器资源非常敏感,会占用较多的CPU核数

CMS默认启动的回收线程数是(处理器核心数量+3)/4,也就是说,如果处理器核心数在四个或以上,并发回收时垃圾收集线程只占用不超过25%的处理器运算资源。

2. 无法处理“浮动垃圾”(Floating Garbage)

在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。所以当老年代使用92%时,就会触发垃圾回收

如果CMS运行期间预留的8%内存无法满足程序分配新对象的需要,就会冻结用户线程的执行,采用SerialOld收集器来重新进行老年代的垃圾收集。

3. “标记-清除”回收算法,会产生大量空间碎片

空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,从而触发GC,可以通过JVM参数进行配置(-XX:CMSFullGCsBefore-Compaction),如果执行了多次不碎片整理的Full GC后执行一次碎片整理的Full GC,从而解决碎片问题。由于这个内存整理必须移动存活对象,用户线程是无法并发的。

4. Garbage First收集器

G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region,可以通过-XX:G1HeapRegionSize调整Region大小,必须是2的次幂,默认是堆1/2000),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。Region中还有一类特殊的Humongous区域(占用多个连续的分区),专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。

G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集,更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。

G1整个过程分为四个步骤:

  1. 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象(G1能够通过TAMS快速确定下一个可用的分配位置,从而减少了分配的时间开销)。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
  2. 并发标记(Concurrent Marking): 在初始标记完成后,G1 GC会进入并发标记阶段。这个阶段与应用程序线程并发执行,通过递归地追踪所有可达的对象,并将它们标记为存活。这个过程是并发的,因此不会阻塞应用程序的执行。
  3. 最终标记(Final Marking):为了处理在并发标记过程中新产生的对象引用关系,G1 GC会执行一次短暂的STW的最终标记。这个阶段确保所有在并发标记阶段漏掉的对象都被正确标记。
  4. 筛选回收(Live Data Counting and Evacuation):负责更新Region的统计数据,对各个Region的 回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,是必须暂停用户线程,由多条收集器线程并行完成的。

注意:G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的。G1从整体来看是基于“标记-整理”算法实现的收集器,但从局部(两个Region之间)上看又是基于“标记-复制”算法实现,不会产生垃圾碎片。

5. JDK8 和 JDK11 默认垃圾回收器

JDK8 默认 Parallel Scavenge 和 Parallel Old。

JDK11 默认 G1。


感谢您的阅读!如果文章中有任何问题或不足之处,欢迎及时指出,您的反馈将帮助我不断改进与完善。期待与您共同探讨技术,共同进步!


http://www.mrgr.cn/news/100504.html

相关文章:

  • 网络:手写HTTP
  • cocos creator使用jenkins打包流程,打包webmobile
  • Python3:Jupyter Notebook 安装和配置
  • Crawl4AI 部署安装及 n8n 调用,实现自动化工作流(保证好使)
  • 【视频生成模型】通义万相Wan2.1模型本地部署和LoRA微调
  • 在 Cursor 中 配置 GitHub MCP Server
  • 【3DMax脚本MaxScript开发:创建高效模型虚拟体绑定和材质管理系统,从3DMax到Unreal和Unity引擎_系列第一篇】
  • 【RabbitMQ消息队列】详解(一)
  • 从视频中学习:从Humanoid-X、UH-1的自动打字幕,到首个人形VLA Humanoid-VLA(自监督数据增强且整合第一人称视角)
  • WPF常用技巧汇总 - Part 2
  • DeepSeek 多头潜在注意力(Multi-Head Latent Attention, MLA)技术
  • STM32F103_HAL库+寄存器学习笔记21 - CAN接收过滤器:CPU减负神器,提升系统效率的第一道防线
  • Drivestduio 代码笔记与理解
  • 论文检索相关网站
  • 大模型API密钥的环境变量配置(大模型API KEY管理)(将密钥存储在环境变量)(python-dotenv)(密钥管理)环境变量设置环境变量
  • 新能源汽车运动控制器核心芯片选型与优化:MCU、DCDC与CANFD协同设计
  • T检验、F检验及样本容量计算学习总结
  • Simulink与C的联合仿真调试
  • Servlet (简单的servlet的hello world程序)
  • 小白自学python第一天