JVM 的垃圾回收器,netty架构图谱

本文介绍了JVM中的几种垃圾回收器,包括ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS和G1。ParNew是CMS收集器的新生代组件,Parallel Scavenge追求高吞吐量。CMS适用于并发场景,但对处理器资源敏感。G1收集器采用Mixed GC模式,以停顿时间为目标,通过Region划分内存并优先处理高价值区域。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ParNew 收集器

   ParNew 收集器实质上时Serial收集器的多线程并行版本,除了同时使用多条线程进行垃圾收集之外,其余的行为包括 Serial收集器可用的所有控制参数(例如 -XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法

Stop The World、对象分配规则、回收策略等都完全一致。

ParNew/Serial Old收集器运行示意图

ParNew/Serial Old收集器运行示意图

   ParNew 收集器除了支持多线程并行收集之外,其他于Serial收集器相比并没有太多创新之处,但它确实不少运行在服务端模式下的HotSpot虚拟机,时JDK 7 之前遗留系统首选的新生代收集器,其中有一个与功能、性能无关但其实

很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作

   ps:JDK 9 开始,ParNew加CMS收集器的组合就不再是官方推荐的服务端模式下的收集器解决方案了。官方细外它能够完全被 G1 所取代甚至还取消了ParNew 加 Serial Old 以及 Serial 加 CMS 这两组收集器组合的支持。

也可以理解为ParNew 合并入CMS成为它专门处理新生代的组成部分。

   -XX:ParallelGCThreads 参数来限制垃圾收集的线程数

Parallel Scavenge 收集器

   Parallel Scavenge 收集器也是一款新生代收集器,它同样是基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器。
   Parallel Scavenge 收集器的特点是它的关注点与其他收集器不同,CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程地停顿时间,而PS 收集器地目标则时达到一个可控地吞吐量(Throughput)。所谓吞吐量就是处理器用于运行用户代码的时

间与处理器总消耗时间地比值 即 吞吐量 = (运行用户代码时间)/(运行用户代码时间 + 运行垃圾收集时间)

   如果虚拟机完成某个任务,用户代码加垃圾收集总耗费了100分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是 99% 。停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提高用户体验;而高吞吐量则可以最高效率

地利用处理器资源,尽快完成程序地运算任务。主要适合在后台运算而不需要太多交互地分析任务。

   Paraller Scavenge 收集器提供了两个参数用于精确控制吞吐量,分别是控制最大垃圾收集停顿时间的 -XX:MaxGCPauseMillis 参数以及直接设置吞吐量大小的 -XX:GCTimeRatio参数
         1.-XX:MaxGCPauseMillis 参数允许的值是一个大于0的毫秒数,收集器将尽力保证内存回收花费的时间不超过用户设置值。不过不要异想天开的认为如果把这个参数值设置的更小就可以使得系统垃圾收集速度变得更快,垃圾收集停顿时间

是以牺牲吞吐量和新生代空间为代价换取的。

         2.-XX:GCTimeRatio 参数的值应时一个大于0 小于100 的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。譬如把此参数设置为 19 ,那允许的最大垃圾收集时间就占总时间的 5 % (即 1/(1+19)),默认值为99。

即允许最大 1%(1/(1+99)) 的垃圾收集时间。

         3.Parallel Scavenge 收集器也经常被称作"吞吐量优先收集器",还有一个参数 -XX:UseAdaptiveSizePolicy 这是一个开关参数,激活之后就不需要人工指定新生代的大小(-Xmn)、Eden 与Survivor区的比例(-XX:SurvivorRatio)、

晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息、动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。这种调节方式称为垃圾手机的自适应调节策略(GC Ergonomics)。

   只需要把基本的内存数据设置号(-Xmx最大堆)然后使用1,2 参数给虚拟机设置一个优化目标,那具体细节参数调节工作就有虚拟机完成。自适应调节也是 Parallel Scavenge收集器区别于 ParNew 收集器的一个重要特征。

Serial Old 收集器

   Serial Old 时Serial收集器的老年代版本,它同样是一个单线程收集器,<font  color="red">使用标记-整理算法。</font> 

这个收集器的主要意义也是共客户端模式下的HotSpot虚拟机使用,

如果在服务端模式下,它也可能有两种用途:

   1.在JDK 5 以及以前的版本种与 Parallel Scavenge收集器搭配使用。
   2.作为 CMS 收集器发生失败时的后备预案,在并发收集发生 Concurrent Mode Failure时使用。

Parallel Old 收集器

   Parallel Old 时Parallel Scavenge 收集器的老年代版本,支持多线程并发收集,基于 **标记-整理算法** 实现。这个收集器时直到 JDK 6 时才开始提供的,"吞吐量优先"收集器终于有了比较名副其实的搭配组合。

CMS 收集器

简述及运行过程

   CMS (Concurrent Mark Sweep)收集器是一种 **以获取最短回收停顿时间** 为目标的收集器。非常适用于关注服务的响应时间,细外系统停顿时间尽可能短,以给用户带来很好的交互。

基于 标记-清除 算法实现 整体过程分为四个步骤包括

   1.初始标记(CMS initial mark)   (STW) 仅仅只是标记一下GC Roots能直接关联到的对象,速度很快
   2.并发标记(CMS concurrent mark)  从直接关联对象开始遍历整个对象图的过程,耗时较长但是不需要停顿用户线程
   3.重新标记(CMS remark)            (STW)是为了修正并发标记期间,因用户线程继续运行而导致标记产生的那一部分对象的标记记录
   4.并发清除(CMS concurrent sweep) 清理删除标记阶段判断的已经死亡的对象

CMS 收集器运行示意图

CMS 收集器运行示意图

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时开启内存碎片的合并整理过

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

程,由于内存整理必须移动存活对象,是无法并发的。这样空间碎片的问

题是解决了但停顿时间又会变长,因此还有一个参数 -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的全部空间。这里的操作涉及存活对象的移动,时必须暂停用户线程,由多条收集器线程并行完成的。

G1 收集器运行示意图

停顿时间模型

   作为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作为老年代的一部分看待。

G1收集器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的卡表就相当简单,只有唯一一份。

而且只需要处理老年代到新生代的引用,反过来则不需要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值