垃圾回收器
Serial(新生代)+ Serial Old(老年代)
特点:
- 单线程垃圾回收器,垃圾回收过程中需要 STW,适用于运行在 Client 模式下的虚拟机;
- 新生代标记复制算法,老年代标记整理算法。
ParNew(新生代)+ Serial Old(老年代)
特点:
- Serial 的并行版本,新生代有多个垃圾回收线程进行回收;
- 垃圾回收的并行是指多个垃圾回收线程同时操作,并发是指垃圾收集线程和用户线程同时执行。
- 新生代标记复制,老年代标记整理。
- 垃圾回收的并发是指垃圾回收线程和客户端线程交替执行;并行是指多条垃圾回收线程执行,用户线程处于等待状态。
Parallel Scavenge(新生代)
特点:
- 重点关注吞吐量(高效率利用CPU,用户线程CPU时间/总CPU使用时间),而 CMS 重点关注停顿时间。
- 新生代采用标记复制算法,老年代采用标记整理算法。
Serial Old(老年代)
Serial 的老年代版本,单线程。
Parallel Old(老年代)
Parallel Scavenge 的老年代版本,使用多线程和标记整理算法,重点关注吞吐量的场景可以使用 Parallel Scavenge + Parallel Old
垃圾收集器搭配使用。
CMS(老年代垃圾收集器)
特点:
- CMS(Concurrent Mark Sweep)重点关注停顿时间。并且是第一款垃圾回收线程和用户线程基本上同时工作的垃圾回收器,在 GC 时不需要 STW。
- 回收过程分为:
- 初始标记:暂停所有线程,记录下与 GCRoots 相连的对象,速度很快;
- 并发标记:同时开启 GC 和用户线程,用一个闭包去记录对象。但是在阶段结束时,不能保证记录了所有可达对象,因为用户线程还处于不断更新状态,所以 GC 线程无法保证可达性分析的实时性;
- 重新标记:暂停所有线程,修正并发标记阶段因为用户程序运行导致标记变动的那部分对象;
- 并发清除:同时开启用户线程和 GC 线程清理对象。
缺点:
- 对 CPU 资源敏感,CMS 工作需要的垃圾回收线程数量=(处理器核数+3)/4,所以当核心数不足4个时,垃圾回收线程占总线程比例过大,CMS 回收时对用户程序影响就很大;
- 无法处理浮动垃圾,最后并发收集阶段用户线程可能产生新的垃圾,而这部分垃圾无法回收必须等待下一轮回收才会被处理掉。浮动垃圾会占据空间,导致可使用内存变少。CMS不会像其它线程那样等待老年代完全占满后再收集,会在老年代占据 68% 左右空间时就开始回收。
- 基于标记清除算法,会产生内存碎片
YoungGC
时由于新生代存活的对象需要晋升到老年代,而老年代由于内存碎片问题导致放不下该对象,就会引发FullGC
。
G1
https://www.cnblogs.com/hongdada/p/14579898.html
https://juejin.cn/post/7050324680875442183?searchId=20240418112735D8AD4AFC02F85174EE86#heading-20
G1 垃圾收集器不同于之前的收集器,它回收的是整个堆(新生代、老年代),其它收集器只会收集其中之一。并且G1 收集器提供了时间预测模型,用户可以通过配置 -XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200
来设置堆内存最大为 32g,设置 GC 最大停顿时间为 200ms。
在系统运行过程中,G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
内存结构
-
Region:
G1 将内存划分为多个大小相等的 Region(默认2048份均分),而不是像其它垃圾收集那样划分为固定大小和数量的分代区域。其中每个 Region 都可以扮演 Edon、Survivor、Old 的角色。Region 大小可以通过
-XX:G1HeapRegionSize
参数指定,一般大小为 2^N(1M~32M)。 -
Humongous:
巨型对象,大小超过了 Region 一半的对象,当线程为巨型分配空间时,不能简单地在 TLAB(每个线程可以独占地一个本地缓冲区空间)分配,因为巨型对象的移动成本高,一个分区可能容不下一个巨型对象。因此巨型对象会在老年代分配,所占用的空间为巨型分区。G1 内部做了优化,一旦没有发现引用指向巨型对象,则可直接在年轻代收集周期中被回收。
垃圾回收的优化
G1 收集器针对垃圾回收的优化包括:对引用对象的标记不需要扫描整堆,而是扫描记忆集;通过写屏障过滤掉不必要的操作,减少写栅栏的开销。
写屏障包括如下:
- G1 垃圾回收时关注应用线程增加对黑色对象到白色对象的引用,当黑色对象新引用了白色对象时,便将这个黑色对象重新设置为灰(采用
pre-write barrier
); - CMS 标记过程中关注线程删除了灰色对象到白色对象的引用,即出现灰色对象删除了白色对象的引用时,就将这个白色对象置为灰色。
- 写屏障不是硬件层面的写屏障,而是软件层面的写屏障,可以理解为在引用赋值这个写操作之前加上一个切面,根据切点的加入时机不同又可以分为
pre-write barrier
、post-write barrier
。
void oop_field_store(oop* field, oop new_value) {
pre_write_barrier(field); // pre-write barrier: for maintaining SATB invariant
*field = new_value; // the actual store
post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference
}
- 记忆集(RememberSet):在串行和并行垃圾回收器中,GC时是通过扫描整堆判断对象是否处于可达的路径中,G1为了避免扫描整个堆,采用记忆集记录所有其它 Region 对当前 Region 的引用情况( YGC 时只需要扫描 Rset 就可以确定 OldRegion 对 yonugRegion 的引用,不需要扫描全部 oldRegion;同理在
MixGC
时会扫描老年代OldRegion
,老年代也会持有一个 Rset(point-in
集合)来记录内部的引用情况)。 - Rset写屏障:写屏障是指每次引用类型在执行写操作时,都会中断操作并执行一些额外的操作,以此过滤掉不必要的写操作。因为写栅栏的开销是很大的,G1 收集器的写屏障是和 Rset 相辅相成的,产生写屏障时会检查写入的引用指向的对象是否和当前引用在不同的 Region,不同的 Region 才通过卡表将相关引用指向对象所在 Region 对应的 Rset 中,通过过滤使 Rset 减少。
- 卡表:如果线程修改了 Region 的内部引用,就必须要通知 Rset,更改其中的记录。但需要注意的是,如果引用的对象很多,赋值器就需要对每个引用做处理,赋值器的开销会很大。因此引入卡表,卡表将一个 Region 在逻辑上划分为若干个固定大小的连续区域,每个区域称之为卡片 Card。默认情况下每个 Card 都没有被引用,当一个地址空间被引用时,地址空间对应的数组索引值被标记为 “0”,Rset 会记录下这个数组的下标。卡表是对记忆集的一种实现。
CMS 中使用卡表的数据结构来标记老年代的某一块内存区域中的对象是否持有新生代对象的引用。JVM 将老年代的对象对应的卡页 CardPage
所在的位置标记为 dirty,这样在执行 YoungGC
时不需要扫描整个老年代对象,而是扫描卡表中被标记为 dirty 的内存区域。
特点:停顿预测模型
Pause Prediction Model
是停顿预测模型,与 CMS 不同的是用户可以设定整个 GC 过程的期望停顿时间,参数为 -XX:MaxGCPauseMillis
指定了 G1 垃圾收集过程目标停顿时间,默认为 200ms,但是它并不是硬性指标,而是期望值。
G1 垃圾回收过程
- 对象分配
- 优先在 TLAB (线程本地缓冲区) 中分配,这样各个线程间不需要任何同步,提高了 GC 效率。但是当线程耗尽了自己的 Buffer 之后,需要申请新的 Buffer。这个时候依然会带来并发的问题。另外 TLAB 可能会有内存碎片的问题。
- TLAB 无法容纳,就在 Eden 区域分配,如果 Eden 无法分配就只能在老年代中分配;
- Humongous 区域分配,连续占用多个 Region的对象,如果发现没有引用指向巨型对象,就可以直接在年轻代收集周期中进行回收。
- 垃圾回收
G1 的垃圾回收过程分为全局并发标记、混合垃圾收集两个步骤:
全局并发标记:
- 初始标记:标记处所有 GC Roots 节点以及直接可达的对象,需要 STW 但是时间很短;
- 并发标记:从 GC Roots 开始对堆对象进行可达性分析,该过程中可能对象间引用关系可能会变化,通过 SATB(snapshot at the begin)结合写前屏障记录下更新的引用对象信息。同时如果发现区域中所有对象都是垃圾会立刻回收;
- 重新标记:重新标记是为了修正并发期间引用发生变化的那部分对象;需要STW;
- 筛选回收:排序各个 Region 的价值和成本,根据用户期望制定回收计划,将回收收益较高的 Region 加入回收集中,清空记忆集并重置已经被清理的空的 Region;
拷贝存活对象:
- 将回收集中存活的对象复制到空的 Region 中,最后清空这些旧的 Region;
CMS 与 G1 对比
- G1 垃圾回收器采用的是标记整理算法,因此空间是连续的;CMS采用的是标记清理,会出现内存碎片问题。因为是连续空间,G1 在分配内存时可以通过 CAS+指针碰撞 方式分配,CMS 只能采取空闲列表方式分配。
- G1 以 Region 为基本单位进行回收,回收时优先进行 youngGC,然后等到老年代空间达到阈值,再次触发 youngGC,接着触发混合GC;
- G1 垃圾回收没有固定时间,可能触发一次 GC 操作后只是完成了整个堆空间垃圾的标记和排序,在未来合适的时间触发垃圾收集。
ZGC
存储结构
ZGC 和 G1 一样采用了分区域的堆内存布局,不同的是 ZGC 的 Region 可以动态创建和销毁,容量也可以动态调整。
ZGC 的 Region 划分为以下三种:
- 小型 Region,容量固定为 2MB;
- 中型 Region,容量固定为 32MB;
- 大型 Region,容量固定为 2MB的整数倍。
ZGC 为了省去卡表的维护操作,标记过程中会扫描全部 Region,如果判定某个 Region 中的存活对象需要被重分配,那么就将该 Region 放入重分配集中。
垃圾收集器搭配组合
YoungGC、MajorGC、FullGC
新生代分为 Eden、Survivor0、Survivor1,当垃圾回收后如果对象的年龄超过了 MaxTenuringThreshold
值,就晋升到老年代。
对象分配和转移
对象是如何在 Eden、Survivor0、Survivor1、Old Generation 之间进行分配和转移的呢?
-
对象初始分配到 Eden 区域,当 Eden 区域无法容纳新对象时,触发一次
Young GC
。
-
Young GC
过程中Eden
区域仍然被引用的对象会被复制到Survivor0
区域。而Eden
区域未被引用的对象将被直接删除。经历过一次YoungGC
后的对象的年龄+1。
-
下一次
YoungGC
时,会重复上述过程,不过这时候Survivor0
和Survivor1
角色交换,交换的目的是将已经使用的Survivor0
中仍然存活的对象复制到Survivor1
中,然后清理剩余空间。
-
如此重复下去,直到新生代剩余存活对象能够执行晋升操作。此时存活对象从新生代的
From Survivor Space
晋升到老年代。
-
随着新生代不断有对象加入到老年代,老年代最终没有足够的空间容纳新晋升对象,此时触发
MajorGC
。
GC 分类
针对 HotSpot 虚拟机的 GC 类型,可以分为如下两大类:
- PartialGC:并不收集整个 GC 堆的模式
- YoungGC:只收集
Young Gen
的 GC; - OldGC:只收集
Old Gen
的 GC,只有 CMS 垃圾收集器拥有这个模式; - MixedGC:混合回收整个
YoungGen
、部分OldGen
,只有 G1 垃圾收集器是这个模式;
- YoungGC:只收集
- FullGC:收集整个堆内存,包括
YoungGen
、OldGen
、PermGen
等所有部分的模式。
各种 GC 触发条件:
YoungGC
:当YoungGen
的Eden
区域满时触发,注意YoungGC
中有部分存活对象会晋升到OldGen
;FullGC
:当准备触发一次YoungGC
发现新生代中要晋升对象的总大小大于当前OldGen
的空间,则不会触发YoungGC
而是触发FullGC
。或者如果有PermGen
,在PermGen
没有足够空间时,触发一次FullGC
。
类加载过程
标签:标记,对象,Region,回收,面试,线程,垃圾,JVM From: https://www.cnblogs.com/istitches/p/18146764