别老说CMS和G1了,该说说ZGC了
大家好,关于别老说CMS和G1了,该说说ZGC了很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!
在执行垃圾收集的那一刻,应用程序需要暂停。
它可以串行或并行收集。
如果我们可以进行并发收集(应用程序不必暂停),那就太好了。
如果收集行为可以被控制,那也是一件美妙的事情。
CMS和G1作为垃圾收集器中的大杀器,需要仔细了解,采访中也经常被问到,但ZGC却很少被提及。
希望您带着以下问题来阅读,带着目标去阅读,收获更多:
为什么没有一个像灵丹妙药一样适合所有场景的优秀收藏家呢?
CMS的优点、缺点和适用场景是什么?
为什么CMS只能作为老年代收集器而不能用于新生代收集?
G1的优点、缺点和适用场景是什么?
ZGC的由来、优缺点?
1 个CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种旨在获得最短收集停顿时间的收集器。这是因为CMS收集器工作时,GC工作线程和用户线程可以并发执行,以达到减少收集停顿时间的目的。
CMS收集器仅作用于老年代收集,并且基于标记和清除算法。其运行过程分为四步:
初始标记(CMS初始标记)
并发标记(CMS并发标记)
备注(CMS备注)
CMS并发扫描
其中,初始标记和重新标记这两个步骤仍然需要Stop-the-world。初始标记只标记GC Roots可以直接关联的对象,而且速度非常快。并发标记阶段是GC Roots Tracing的过程,重新标记阶段是为了纠正并发标记期间用户程序继续运行而造成的标记。对于对象变化部分的标记记录,该阶段的停顿时间一般比初始阶段稍长,但比并发标记时间短很多。
CMS以管道方式分割收集周期,保持长时间操作单元与应用程序线程并发执行。只隔离那些需要执行STW的运行单元,控制这些单元在适当的时间运行,并确保它们能够在短时间内完成。这样整个收集周期只有两次短暂的停顿(初始标记和重新标记),实现了近似并发。
CMS收集器的优点:并发收集、低暂停。
CMS收集器的缺点:
CMS收集器对CPU资源非常敏感。
CMS收集器无法处理浮动垃圾(Floating Garbage)。
CMS收集器基于标记和清除算法,该算法有其缺点(内存碎片)。
暂停时间是不可预测的。
CMS收集器能够实现并发的根本原因是它采用了“标记-清除”算法,并对算法过程进行了细粒度的分解。正如上一章介绍的那样,标记清除算法会产生大量的内存碎片,这对于新一代来说是无法接受的。因此,新一代收集器不提供CMS版本。
注意:说CMS是老年代收集器并不是很准确。 CMS的每个收集过程实际上都是一次涉及年轻代和老年代的综合垃圾收集器。在很多文章和书籍中,CMS被分为老年代垃圾收集器。另外,它主要作用于老年代。所以一般都会被误认为是这样。
另外,我要补充一点,当JVM暂停时,需要选择合适的时间。由于JVM系统在运行过程中比较复杂,不可能随时暂停,因此引入了安全点的概念。
安全点
安全点,即程序执行过程中不能处处停止和启动GC。只有达到安全点才可以暂停。安全点选择既不应该太频繁,以免导致GC 等待太长时间,也不应该太频繁,以免过度增加运行时负载。
安全点的最初目的不是为了停止其他线程,而是为了找到一个稳定的执行状态。在这个执行状态下,Java虚拟机的栈不会发生变化。这允许垃圾收集器“安全”地执行可达性分析。只要不离开这个安全点,Java 虚拟机就可以在垃圾收集发生时继续运行这个本机代码。
程序运行时,并不会处处停止和启动GC。只有达到安全点才可以暂停。安全点的选择基本上是根据程序是否具有允许程序长时间执行的特性。 “长执行”最明显的特征就是指令序列的复用,比如方法调用、循环跳转、异常跳转等,因此具有这些功能的指令会产生安全点。
关于安全点,另一个需要考虑的问题是如何让所有线程(不包括执行JNI调用的线程)“运行”到最近的安全点,然后在GC发生时停止。
两种解决方案:
抢占式暂停抢占式中断不需要线程的执行代码主动配合。当GC发生时,所有线程首先被中断。如果发现线程被中断的地方不在安全点,则恢复线程并让它“运行”到安全点。现在几乎没有虚拟机使用此方法来暂停线程以响应GC 。。。
主动中断(Voluntary Suspension) 主动中断的思想是,当GC需要中断某个线程时,它并不直接对该线程进行操作,而是简单地设置一个标志。每个线程执行时都会主动轮询这个标志,发现中断标志为true。当时间到来时,它会中断并自行挂起。轮询标志的位置与安全点重合,加上创建对象需要分配内存的位置。
安全区
这意味着在一段代码内部,引用关系不会改变。在该区域的任何位置启动GC 都是安全的。安全区域也可以看作是安全点的扩展。
2 G1 收藏家
G1重新定义了堆空间,打破了原来的分代模型,将堆划分为区域。这样做的目的是收集不必在整个堆内执行,这是它最显着的特点。区域分区的好处是它带来了具有可预测暂停时间的收集模型:用户可以指定收集操作需要多长时间才能完成。即G1提供了近乎实时的采集特性。 G1的主要关注点是实现停顿时间可控,并在此基础上最大化吞吐量。
G1使用暂停预测模型来满足用户指定的暂停时间目标,并根据目标选择进行垃圾收集的块数。 G1使用增量回收,一次回收一些块,而不是回收整个堆。需要明确的是,G1不是实时收集器(它只是接近实时)。它会尽力满足我们的暂停时间要求,但也不是绝对的。它是根据之前垃圾收集的统计数据,估计在用户指定的暂停时间内可以收集到的。有多少块。
G1与CMS的特性对比如下:
特性G1 CMS 并发和分代是是最大化可用堆内存是否低延迟是是吞吐量高低压缩是否可预测性强弱年轻代和老年代的物理隔离否是
G1具有以下特点:
并行并发:G1可以充分利用多CPU、多核环境下的硬件优势,利用多个CPU来缩短Stop-the-world停顿时间。其他一些收集器原本需要暂停Java线程执行的GC操作。 G1收集器Java程序仍然可以继续并发运行。
按世代收藏
可预测的暂停:这是G1 相对于CMS 的优势。减少暂停时间是G1 和CMS 的共同关注点。
G1之前的其他收集器收集了整个新生代或老年代,但G1不再是这种情况。在设计堆结构时,G1打破了以往将收集范围固定在新生代或老年代的模式。 G1收集器将整个Java堆划分为多个大小相等的独立区域(Region)。 Region是具有连续地址的内存空间。 G1模块的组成如下图所示:
G1堆的区域布局.png
虽然新生代和老一代的概念仍然保留,但新生代和老一代不再是物理上隔离的。它们都是Region 的一部分的集合(不需要是连续的)。 Region的大小是一致的,其值为1M到32M字节之间的2的幂。 JVM会尝试划分大约2048个相同大小的Region。为此,请参考以下源代码。其实这个数字是可以手动调整的,G1会根据堆大小自动调整。
#ifndefSHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPP#defineSHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPP#include'memory/allocation.hpp'classHeapRegionBounds:publicAllStatic{private://最小区域大小;我们不会低于这个值。//我们可能想在未来减少这个,以应对withsmall//heapsabitmoreefficiently.staticconstsize_tMIN_REGION_SIZE=1024*1024;//Maximumregionsize;wedon'tgohigherthanthat.There' sagood //有上限的原因。我们不希望区域太大//太大,否则清理的效率会降低//标记后找到完全空区域的机会会更少。staticconstsize_tMAX_REGION_SIZE=32*1024*1024;//自动区域大小计算将尝试在堆中拥有围绕这个//许多区域(基于最小堆大小).staticconstsize_tTARGET_REGION_NUMBER=2048;public:staticinlinesize_tmin_size();staticinlinesize_tmax_size () ;staticinlinesize_ttarget_number();};#endif//SHARE_VM_GC_G1_HEAPREGIONBOUNDS_HPP
G1 收集器能够对可预测的暂停时间进行建模,因为它可以系统地避免整个Java 堆上的全方位垃圾收集。 G1将通过合理的计算模型计算并量化每个Region的收集成本。这样,在给定“暂停”时间限制的情况下,收集器始终可以选择一组合适的Region 进行收集。目标就是让收集开销满足这个约束,从而达到实时收集的目的。
对于计划从CMS 或ParallelOld 收集器迁移的应用程序,根据官方建议,如果发现它们满足以下特征,可以考虑将其替换为G1 收集器,以追求更好的性能:
实时数据占用一半以上的堆空间;
对象分配或“提升”的比率发生显着变化;
期望消除长GC 或暂停(超过0.5——1 秒)。
原文如下: 如果应用程序具有以下一个或多个特征,则当前使用CMS 或ParallelOld 垃圾收集器运行的应用程序将有利于切换到G1。
超过50% 的Java 堆被实时数据占用。对象分配率或提升率差异很大。不需要的长时间垃圾收集或压缩暂停(超过0.5 到1 秒)
G1集合的操作流程大致如下:
初始标记:只需标记GC Roots可以直接关联的对象,并修改TAMS(Next Top at Mark Start)的值,这样下一阶段用户程序并发运行时,就可以处于正确的可用Region中。要创建一个新对象,这个阶段需要“暂停线程”,但需要的时间很短。
并发标记:从GC Roots开始,分析堆中对象的可达性,以找到幸存的对象。该阶段需要较长时间,但可以与用户程序并发执行。
最终标记:是纠正并发标记时由于用户程序的继续运行而导致标记记录发生变化的部分。虚拟机将这段时间的对象变化记录在线程Remembered Set Logs中。最后的标记阶段需要将Remembered Set Logs的数据合并到Remembered Set中。该阶段需要“暂停线程”,但可以并行执行。
筛选与回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序,并根据用户期望的GC停顿时间制定回收计划。该阶段也可以与用户程序并发执行,但由于只回收一部分Region,所以时间是用户可控的,暂停用户线程将大大提高收集效率。
图片.png
G1 比ParallelOld 和CMS 需要更多的内存消耗。那是因为记账消耗了一部分内存,比如下面两个数据结构:
Remembered Sets:每个块都有一个RSet,用于记录进入该块的对象引用(例如块A中的对象引用了块B,则块B的Rset需要记录此信息)。它用于并行化收集过程并使块能够独立收集。
卡桌
有一种场景,老年代的对象可能会引用新生代的对象。标记存活对象时,需要扫描老年代中的所有对象。因为该对象保存了新生代对象的引用,所以这个引用也称为GC Roots。这是否意味着我们必须再次进行全堆扫描?成本太高了。
HotSpot提供的解决方案是一种称为Card Table的技术。该技术将整个堆划分为大小为512字节的卡片,并维护一个卡片表来存储每个卡片的一个标识位。该标志指示相应的卡是否可以具有对新一代对象的引用。如果可能的话,我们就认为该卡脏了。
进行Minor GC时,我们不需要扫描整个老年代,而是在card表中查找脏卡,并将脏卡中的对象添加到Minor GC的GC Roots中。当完成对所有脏卡的扫描后,Java虚拟机将清除所有脏卡的标志位。
为了保证每一张可能引用新生代对象的卡都被标记为脏卡,Java虚拟机需要拦截每一个引用实例变量的写操作,并执行相应的写标志操作。
使用卡表可以减少老年代的全堆空间扫描,可以大大提高GC效率。
我们可以看一下官方文档对G1的展望(这个英文描述比较简单,就不翻译了):
Future:G1 计划作为并发标记-清除收集器(CMS) 的长期替代品。将G1 与CMS 进行比较,有一些差异使G1 成为更好的解决方案。一个区别是G1 是一款压缩型收集器。 G1 足够紧凑,完全避免使用细粒度的空闲列表进行分配,而是依赖于区域。这大大简化了收集器的各个部分,并且基本上消除了潜在的碎片问题。此外,G1 提供比CMS 收集器更可预测的垃圾收集暂停,并允许用户指定所需的暂停目标。
3 中关村
ZGC(Z Garbage Collector)作为一种比较新的收集器,目前还没有受到广泛的关注。作为一个低延迟的垃圾收集器,它有以下几个亮点:
暂停时间不会超过10ms
暂停时间不会随着堆的增长而增加(控制暂停时间在10ms以内)
支持多种堆大小(8MB-16TB)
在ZGC中,连堆空间在逻辑上都重新定义了(不区分年轻代和老年代),只是分成了页块。每次GC时都会对页面进行压缩,因此不存在碎片问题。虽然ZGC是一项非常新的GC技术,但其优点并不一定突出。 ZGC仅在特定情况下具有绝对优势,例如巨大的堆和极低的暂停要求。事实上,大多数开发者对这两方面(尤其是服务器端)问题不大,而是更关心GC的性能/效率。还有一种观点认为ZGC是针对大内存和多CPU而设计的,它使用分区来减少STW。
ZGC仅支持JDK14之前的Linux,从JDK14开始支持Mac和Windows。
您可以通过官网查看ZGC的变更日志:
JDK 15(正在开发中)
提高NUMA 意识
支持类数据共享(CDS)
支持将堆放置在NVRAM 上
JDK 14
macOS 支持(JEP 364)
Windows 支持(JEP 365)
支持微小/小型堆(低至8M)
支持JFR 泄漏分析器
支持有限且不连续的地址空间
并行预触摸(使用-XX:+AlwaysPreTouch时)
性能改进(克隆内在等)
稳定性改进
JDK 13
最大堆大小从4TB 增加到16TB
支持取消提交未使用的内存(JEP 351)
支持-XX:SoftMaxHeapSIze
支持Linux/AArch64平台
缩短到达安全点的时间
JDK 12
支持并发类卸载
进一步减少暂停时间
JDK 11
ZGC初始版本
不支持类卸载(使用-XX:+ClassUnloading没有效果)
可见ZGC的未来是有希望的,让我们拭目以待。
用户评论
终于有人提起了ZGC!感觉这几年都被CMS和G1刷屏了,对性能优化的新型方案太冷落了。确实,ZGC的低延迟停顿吸引很多开发者了,而且它还有更丰富的生态系统可以支撑。
有11位网友表示赞同!
别总说CMS、G1,改聊聊ZGC?我觉得这很值得探讨!毕竟技术发展日新月异,不能总是固步自封。最近我一直在学习ZGC的用法,发现它真的很有潜力,尤其是对于高并发web应用来说更胜一筹。
有19位网友表示赞同!
我一直觉得CMS、G1已经够好了,每次停顿时间都能有效控制,再想追求极致低延迟?我觉得有些矫枉过正了。而且,ZGC目前用的并不多见,学习成本可能也是一个问题吧。
有15位网友表示赞同!
我赞同这个观点!确实很多文章都忽略了ZGC,说来说去都是CMS和G1的优缺点复读。不过ZGC在新生代回收策略上的创新确实很值得关注,相信未来会越来越多的人使用它。
有5位网友表示赞同!
我一直觉得 ZGC 的停顿时间太短了,对许多应用程序来说已经足够用了,わざわざ学习新的垃圾收集方案是不是有些鸡肋呢?
有10位网友表示赞同!
说白了,很多开发者还是习惯了CMS和G1的用法,难以接受全新的ZGC。需要更多的资料和实践案例来普及ZGC的使用,吸引更多人加入到这个潮流中。
有12位网友表示赞同!
我个人认为,在实际应用场景中,CMS/G1 和 ZGC 的优缺点是相辅相成的。对于一些对性能要求极高的应用程序来说,或许只有ZGC能满足需求;而对于大多数中小企业,CMS/G1已经足够了。
有5位网友表示赞同!
这个文章说的没错, 我其实也尝试过使用 ZGC,觉得它的低延迟停顿确实非常吸引人,尤其是处理一些紧急请求的时候,表现得更加出色。
有16位网友表示赞同!
对,ZGC的停顿的确很短,但是对于现在已经成熟的CMS和G1来说,这个优势是否能彻底颠覆现状还说不上,需要看它在更大规模环境下的表现如何。
有15位网友表示赞同!
其实 CMS 和 G1 已经有很好的基础了,开发社区也比较活跃,学习资料充足,突然要转行使用 ZGC,确实需要一些时间和精力去调整。
有7位网友表示赞同!
我从业这么多年始终认为,技术的选择应该根据实际需求来决定. 如果一款技术能有效解决你的问题,并且让你的工作效率更高,那它就值得推荐!无论它是什么框架或算法。
有7位网友表示赞同!
我最近也在学习ZGC,感觉它的运行机制和实现思路与传统垃圾回收器还是有些不同的,需要花点时间去适应。不过对于追求更极致性能的应用场景来说,学习ZGC确实是很有价值的投资。
有17位网友表示赞同!
同意标题!CMS 和 G1 被炒上天了,确实应该聊聊 ZGC 的进步。它在一些场景下的表现真的很出色,也许未来几年会成为主流替代品?
有12位网友表示赞同!
这篇文章太现实了,开发者的选择往往是折中和平衡的。如果说 ZGC 已经是最佳方案,那我觉得还是需要更多的数据和实证来证明!
有11位网友表示赞同!
现在很多公司都在测试和使用 ZGC ,我也有朋友在企业内将项目迁移到 ZGC 框架下. 总而言之, 新技术的发展值得我们探索和尝试,相信随着时间的推移它会更加成熟完善。
有18位网友表示赞同!
我觉得这个文章提了一个很好的观点。其实很多时候,我们应该多关注一些新兴的技术方案,而不是总是停留在一种既定的思维模式中。只有不断地探索和进步,才能让我们的技术始终走在前沿!
有12位网友表示赞同!