CMS 优点及其缺点
优点
CMS 是一款优秀的收集器,它最主要的优点在名字上已经体现出来:**并发收集、低停顿**
缺点
CMS 收集器时HotSpot虚拟机追求低停顿的第一次成功尝试,但是它还远达不到完美的程度,至少有以下三个明显的缺点:
CMS 对处理器资源非常敏感
事实上,面向并发设计的程序都对处理器资源比较敏感,在并发阶段它虽然不会导致用户线程停顿,但却会因为占用了一部分线程而导致应用程序变慢,降低总吞吐量。CMS 默认启动的回收线程数是(处理器核心数量+3)/4,也就是说,如果处理器核心数
在四个或以上,并发回收时垃圾收集线程只不过占用不超过25% 的处理器运算资源,并且会随处理器核心数量的增加而下降。但当处理器核心数量不足四个时,CMS 对用户程序的影响就肯恩变得很大。未来缓解这种情况,虚拟机提供了一种称为“增量式并
发收集器”的CMS收集器变种,在并发标记、清理的时候让收集器线程、用户线程交替运行,尽量减少垃圾收集线程的独占资源的时间。JDK 7 i-CMS模式已经被声明为"deprecated" JDK 9 被完全废弃
CMS 无法处理"浮动垃圾" 及 需要注意 CMS触发时间以及发生"并发失败"
在CMS 的并发标记和并发清理阶段,用户线程还在继续运行的,程序在运行过程自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS 无法在当此收集种处理掉它们,只要留待下一次垃圾收集时再清理掉。
这一部分垃圾就称为"浮动垃圾"。同样也是由于在垃圾收集阶段用户线程还需要持续运行,那就还需要预留足够内存空间提供给用户线程使用,因此 CMS 收集器不能像其他收集器那样等待到老年代几乎完全被填满在进行收集,必须预留一部分空间供并发
收集时的程序运作使用。
JDK 5 的默认设置下,当老年代使用 68% 的空间后就会被激活。可以适当调用参数 -XX:CMSInitiatingOccu-pancyFraction 的值提高 CMS 出发百分比降低内存回收频率,获取更好的性能,到JDK 6时 CMS 启动阈值已经默认提升至 92% ,但
这又会更容易面临另一种风险,要是 CMS 运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次 “并发失败”(Concurrent Mode Failure)这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用Serial Old 收集器来重新进行
老年代的垃圾收集,但这样停顿时间就很长了,所有上述参数设置太大将会很容易导致大量的并发失败产生,性能反而降低。
采用"标记-清除"算法造成空间碎片过多 造成大对象分配问题
使用该算法意味着手机结束时会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很多剩余空间,但就是无法找到足够大的连续空间来分配当前对象,而不得不提前触发一次 Full FC 的情况。为了解决
这个问题,CMS 收集器提供了一个 -XX:UseCMSCompactAtFullCollection 开关参数(默认开始 JDK9 开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于内存整理必须移动存活对象,是无法并发的。这样空间碎片的问
题是解决了但停顿时间又会变长,因此还有一个参数 -XX:CMSFullGCsBeforeCompaction(默认值为0,每次进入Full GC 都进行碎片整理 ,JDK9 开始废弃),参数要求CMS收集器在执行若干次不整理空间的 Full GC后,下一次进入FullGC就会先进行碎
片整理。
Garbage First 收集器
简述
Garbage First(简称G1)收集器时垃圾收集器技术发展历史上里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形态,JDK 8 Update 40 的时候,G1提供并发的类卸载的支持,补全了其计划功能的最后一块拼图。
这个版本的 G1 收集器才被Oracle 官方称为"全功能的垃圾收集器"(Full-Featured Garbage Collector)。
G1 是一款主要面向**服务端应用**的垃圾收集器。JDK 9 发布之日,G1宣告取代Parallel Scavenge加Parallel Old 组合,成为服务端模式下的默认垃圾收集器,而CMS 则沦落至被声明伟不推荐使用(Deprecate)的收集器。
运行过程
1.初始标记: 仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可以用Region中分配新对象。这一阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,
所有G1 收集器在这个阶段实际并没有额外的停顿
2.并发标记: 从GC Roots开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找到要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。
3.最终标记: 对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的SATB记录。
4.筛选回收: 负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来指定回收计划,可以自由选择任意多个Region构成会收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再
清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,时必须暂停用户线程,由多条收集器线程并行完成的。
停顿时间模型
作为CMS收集器的替代者和继承人,设计者们希望做出一款能够建立起"停顿时间模型"(Pause Prediction Mdel)的收集器,停顿时间模型的意思时能够支持指定在一个长度为 M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒这样
的目标。那具体如何实现这个目标呢?首先要有一个思想上的改变,在G1 收集器出现之前的所有收集器,包括CMS在内,垃圾收集器的目标范围要么是整个新生代(Minor GC),要么就是整个老年代(Major GC),在要么就是整个Java堆(Full GC)。而G1跳出
这个樊笼,它可以面向堆内存任何部分来组成会收集(Collection Set 一般简称CSet)进行回收。衡量标准不再是它属于哪个分代,而是哪块内存中存放的垃圾数量最多,回收收益最大,这就是G1收集器的Mixed GC模式。
Region
G1 开创了基于Region 的堆内存布局是它能够实现这一目标的关键。虽然G1也仍遵循分代收集理论设计的。但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续额Java堆划分为多个大小
相等的独立区域(Region),每个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理,这样无论是新创建的对象还是以及存活了一段时间、熬过多次收集的就对象>
都能获取很好的收集效果。
Humongous(存储大对象)
Region中还有一类特殊的Humongous区域,专门用来存储大对象。G1认为只要大小超过一个Region容量的一半的对象即可判断为大对象。每个Region的大小可以通过参数 -XX:G1HeapRegionSize设置,取值范围为1MB~32MB,且应为2的N次幂。
而面对那些超过整个Region容量的超级大对象,将会被存放在N个连续的HumongousRegion之中,G1中大多数行为都把Humongous Region作为老年代的一部分看待。
-XX:MaxGcPauseMillis
G1虽然任然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,它们都是一系列区域的动态集合。G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的
整数倍,这样可以有计划地避免在整个Java堆中尽心全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾堆积的"价值"大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据
用户设置的允许的收集停顿时间(参数-XX:MaxGCPauseMillis指定,默认200毫秒),优先处理回收价值收益最大的那些Region,也就是"Garbage First"名字的由来。—这种使用Region划分内存空间,以及具有优先级的区域回收方式,保证了G1收集器在
有限时间内获取尽可能高的收集效率。
G1 潜在问题
如何解决多Region存在的跨Region引用
解决思路使用 [记忆集]( ) 避免全堆作为 GC Roots扫描,在G1收集器上记忆集的应用其实要复杂很多,它的每个Region都维护有自己的记忆集,这些记忆集会记录下别的Region指向自己的
指针并标记这些指针分别在那些卡页的范围之内。
G1的记忆集在存储结构的本质上是一种哈希表,Key是别的Region的起始地址,Value是一个集合,理论存储的元素是卡表的索引号。这种"双向"的卡表结构(卡表是"我指向谁",这种结构还记录着"谁指向我")比原来的卡表实现起来更复杂,同时由于Region
数量比传统收集器的分代数量明显要多得多,因此G1收集器要比其他传统垃圾收集器有着更高的内存占用负担。根据经验,G1至少要消耗大约相当于Java堆容量10%至20%的额外内存来维持收集器工作
并发标记阶段如何保证收集线程与用户线程互不干扰
这里首先要解决的是用户线程改变对象引用关系时,必须保证其不能打破原本的对象图结构:CMS 收集器次啊用增量更新算法实现,而G1收集器则是通过原始快照(STAB)算法来实现。此外,垃圾收集堆用户线程的影响还体现在回收过程中新创建对象的
内存分配上,程序要继续运行就肯定会持续有新对象被创建,G1为每一个Region涉及了两个名为TAMS(Top at Mark Start)的指针,把Region中的一部分空间划分出来用于并发回收过程中新对象分配,并发回收时新分配的对象地址都必须要在这两个指针位置
以上。G1收集器默认在这个地址上的对象是被隐式标记过的,即默认它们是存活的,不纳入回收范围。与CMS中"Concurrent Mode Failure"失败会导致Full GC 类似,如果内存回收的速度赶不上内存分配的速度,G1收集器也要被迫冻结用户线程执行,导致
Full GC而产生长时间"Stop The World"
怎么建立起可靠的停顿预测模型
用户通过-XX:MaxGCPauseMillis参数指定的停顿时间只意味着垃圾收集发生之前的期望值,但G1收集器要怎么做才能满足用户的期望呢?G1收集器的停顿预测模型是以衰减均值(Decaying Average)为理论基础来实现的,在垃圾收集过程中,G1收集器
会记录每个Region的回收耗时、每个Region记忆集里的脏卡数量等各个可测量的步骤花费的成本,并分析得出平均值、标准偏差、置信度等统计信息。话句话说,Region的统计状态越新越能决定其回收的价值。然后通过这些信息预测现在开始回收的话,有哪些
Region组成的回收集才可以在不超过期望停顿时间的约束下获得最高的收益。
G1 对比 CMS
优点
指定最大停顿时间、分Region的内存布局、按收益动态确定回收集
G1 整体采用 "标记-整理" 算法实现,运行期间不会产生内存空间碎片。垃圾收集完成之后能提供规整的可用内存。有利于程序长时间运行,在程序为大对象分配内存时不容易因无法找到连续内存空间而提前出发下一次收集
缺点
G1无论是为了垃圾收集产生的内存占用还是程序运行的额外执行负载都要比CMS要高
内存占用
G1 和 CMS 都是用卡表来处理跨代指针,但G1的卡表实现更为复杂,而且堆中每个Region,无论扮演老年代还是新生代,都必须有一份卡表,这导致G1的记忆集可能会占用整个堆容量的20%乃至更多的内存空间;相比起来CMS的卡表就相当简单,只有唯一一份。
而且只需要处理老年代到新生代的引用,反过来则不需要。
执行负载
由于两个收集器各自的细节实现特点导致了用户程序运行时的负载会有不同,譬如:
它们都是用到了写屏障,CMS用写后屏障来更新维护卡表;而G1除了使用写后屏障来进行同样的卡表维护操作外,为了实现原始快照搜索算法,还需要使用写前屏障来跟踪并发时的指针变化情况。
低延迟垃圾收集器
衡量垃圾收集器的三项最重要的指标是:内存占用(Footprint)、吞吐量(Throughput)和延迟(Latency),三者最多可以同时达成其中的两项。
Shenandoah 收集器
相比G1 的改进
1.支持并发的整理算法
2.没有使用分代收集
3.使用"连接矩阵"来记录跨Region的引用关系
运行过程九阶段
初始标记: 首先标记与GC Root是直接关联的对象
并发标记: 遍历对象图,标记出全部可达的对象
最终标记: 处理剩余SATB扫描,并在这个阶段统计回收价值最高的Region,构成一组回收集
并发清理: 用于清理那些整个区域内连一个存活对象都没有找到的Region
并发回收: 将回收集里面的存活对象复制一份到其他未被使用的Region中。使用读屏障和转发指针应对并发问题
初始引用更新: 并发回收结束后把堆中所有指向旧对象的引用修正到复制后的新地址。实际并未做什么,只是为了建立一个线程集合点确保上步任务完成
并发引用更新: 真正开始进行引用更新操作。
最终引用更新: 修正GC Roots中的引用
并发清理: 整个回收集中所有Region再无存活对象,直接清空以便之后使用
ZGC 收集器
特点
内存布局
ZGC的Region具有动态性---动态创建和销毁,以及动态的区域容量大小。
小型Region: 容量固定为2MB,用于放置小于256KB的小对象
中型Region: 容量固定32MB,用于放置大于等于256KB但小于4MB的对象
大型Region: 容量不固定,可以动态变化,但必须是2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象。因为复制一个大对象的代价非常高昂,所有不会被重分配,
并发整理算法的实现
染色指针
运行过程
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-7z2nU5qZ-1715824300827)]
[外链图片转存中…(img-9roisAwg-1715824300828)]
[外链图片转存中…(img-hCuOkP8B-1715824300828)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!