JVM面试题(一):垃圾收集器和垃圾回收理论

在这里插入图片描述

原文详细地址:JVM面试题(一):垃圾收集器和垃圾回收理论

原文详细地址:JVM面试题(一):垃圾收集器和垃圾回收理论

原文详细地址:JVM面试题(一):垃圾收集器和垃圾回收理论

原文详细地址:JVM面试题(一):垃圾收集器和垃圾回收理论

欢迎关注我的公众号【意姆斯Talk】来聊聊Java面试,对线面试官系列持续更新中!


垃圾回收理论

标记-复制算法

图片

概念:将内存区域划分成两块,一块空闲区域,一块存储区域,在存储区域中标记要回收的对象,然后把未标记(存活)的对象都移动到空闲区域,每次回收都是对一块区域中的内存进行回收。

缺点

效率问题:如果标记的对象太多,执行起来费时。

内存浪费:会将内存划分成两块,一块内存永远都是空闲置。

适用于年轻代,年轻代一般分为eden,s1,s2区,新创建的对象都是在eden区中分配内存空间,当进行一次minorGc后,存活的对象都移动到S1,未存活的对象都在eden区中被回收了。假设又进行一个minorGc,把s1区中和eden区中的存活对象都移动到S2,反反复复移动,s1和s2总有一个区是空闲的。

跟redis的字典有点类似,字典中存在两个HashTable,一个用来空闲,一个用来存储数据,当发生rehash时,也是把一个空间的数据(对象)移动到一个空闲区域。

标记-清除算法

图片

概念:只有一块内存区域,标记的意义是标记存活或未存活的对象,只能选一种,假设标记存活的对象,然后把未标记的对象都进行垃圾回收,回收后内存地址很多都是零零散散的,不是连续的,用计算机术语:留下了很多内存碎片。像教室,老师随机抽查人出去罚站,教室留下的位子都是零散的。

缺点

效率问题:如果需要标记的对象太多,执行的时间就会越长,效率不高

内存问题:产生了很多内存碎片。

衍生了一个面试题:CMS的老年代就是使用的标记-清楚算法,CMS垃圾回收的碎片解决方式是什么?

标记-整理算法

图片

概念:是标记-清除的升级版,会帮我们整理好内存碎片。

缺点

标记问题:如果需要标记的太多,执行的时间就越长,效率不高

STW问题:在整理内存碎片期间,是不允许用户线程允许的,会STW。

对象引用的判断
引用计数器
每个对象都保存一个整型的计数器属性,如果被其他对象引用,则计数器+1,如果引用失效,则计数器-1,当计数器为0时,说明对象不存在任何引用,固可以当成垃圾对象给回收(gc)掉了。
缺点
无法解决两个对象相互引用的问题。

可达性分析算法(三色标记)
以gc roots为根(起始点),遍历gc roots的子节点,使用可达性分析算法,将扫描过的对象都用引用链连接起来,如果gc roots没有任何的直接/间接引用某个对象,则该对象是垃圾对象。

黑色:当前对象所有的引用都被垃圾回收器扫描过了
灰色:当前对象部分的引用被垃圾回收器扫描过了。
白色:当前对象的引用从来没有被扫描过。
缺点
带来多标和漏标的隐患
多标:本来要回收的对象未被回收,称之为浮动垃圾,问题不大,下一次再扫描,如果能回收就标记为垃圾对象。

漏标:不应该回收的对象却被回收了,一般漏标存在两个必要条件

1:新增了黑色对象对白色对象A引用(指向)
2:删除了灰色对象对白色对象A引用(指向)

原始快照:打破第二条规则,把删除的引用记录提前下来,即灰色对象要删除白色对象的引用记录下来,重新扫描一次灰色对象和白色对象。

增量更新:打破第一条规则,把存在新增引用数据,会在最终扫描阶段,重新扫描一次,把当前的黑色对象的根对象进行深度扫描,可以简化理解成:黑色对象一旦指向白色对象,它就变成了灰色对象,需要重新扫描。

三色标记流程图如下

图片

标记过程
1:初始时,ABCDEFGH都是白色的。
2:将Gc roots直接引用到的对象扫描,先变成灰色对象。

3:从灰色对象继续找其他引用,直至–>引用箭头找不到下一个节点,最终全部变成黑色对象,剩下的白色对象就是垃圾对象。

gc roots:一般是栈中引用的对象,被static修饰的对象,常量引用的对象等等。

GC的本质就是:从 GC roots扫描,若存在不可达对象,然后执行对象的finalize()方法,然后再垃圾回收这些白色对象。

finalize()像个复活甲,如果回收的对象被引用链上的对象引用,则不回收。

垃圾收集器
垃圾收集器具备三项核心指标:延迟,吞吐量,内存空间,想要三者都具备目前很难实现,但延迟和吞吐量可以优先保证。

Serial垃圾收集器
单线程的垃圾回收器,适用于单核的机器,年轻代采用标记-复制算法,老年代采用标记-整理算法,标记-整理会带来STW。

Serial old
serial老年代的版本,也是CMS兜底的方案,当CMS并发清理时,若存在大对象未能存储进预存空间中,则并发失败,采用Serial old来清理老年代,带来STW的风险,停顿时间无法评估。

ParNew
多线程的垃圾回收器,年轻代采用标记-复制算法,老年代采用标记-整理算法,标记-整理会带来STW。

parallelScavenge+parallelOld
关注吞吐量和垃圾回收效率,并不是垃圾收集时间越短,吞吐量越高越好,当回收垃圾时间都赶不上对象生成时间,还是会造成OOM的。
吞吐量=用户允许代码时间(用户允许代码时间+垃圾回收时间)
具备三个核心:设置吞吐量大小,设置回收停顿时间,具备动态调整机制。
动态调整机制无需设置Service区和Eden区的比例,也无需关系多大的大对象直接进入老年代,具备自动协调+动态调整能力,会STW

下面的垃圾收集器,已经平分秋色了,基本上都是三足鼎立的存在。
主要掌握CMS和G1。
Shenandoah,ZGC了解即可。

CMS垃圾收集器
概念
CMS垃圾回收器是以最短停顿时间为目标,提高用户的交互体验。

步骤

初始标记

    STW,速度很快,标记Gc Roots直接关联的对象,停顿时间很快, 因为跟Gc roots直接关联的对象比所有的对象肯定少。

并发标记:

    用户线程和垃圾回收线程同时进行,用户线程产生新的对象,垃圾回收线程来标记对象,这里就存在多标和漏标的问题,CMS解决漏标的方法是增量更新,记录下黑色对象新增白色对象的引用,在最终标记中重新扫描。

最终标记

STW,重新扫描增量更新中的引用对象。

并发清除

清除标记的未存活的对象,GC后存在很多内存碎片。

使用
1:一般使用CMS来收集Old。
2:为了避免频繁并发失败问题,设置老年代内存达到多少百分比可触发CMS垃圾回收,假设设置参数为96%,当内存达到了96%,预存空间4%,当预存空间无法放置新来的大对象,则会进行serial old来清理老年代,会触发STW,使用标记-整理算法。
3:CMS回收线程数=(Cpu数量+3)/4。
4:使用一个记忆集+卡表的数据结构来记录老年代指向新生代的引用,防止扫描整个老年代。

缺点
内存碎片问题

    CMS采用的是标记-清除算法,此算法带来的缺点之一就是产生很多内存碎片,当对象进入老年代,若此对象无法存放在预存空间中,则会造成serial old整理内存空间,如果预存空间足够存放,则判断当前老年代是否能放下此对象,若放不下,则进行Full GC,Full GC时,可采用两个参数,一个参数是控制开启或关闭,在CMS收集器进行FullGC,开启内存碎片的合并整理过程,移动存活对象,一个参数是控制次数,CMS收集器在执行了规定的次数后,下一次FullGC便会进行内存碎片处理。前者参数默认是开启的,后者参数默认是0。

空间预留问题

    在并发清除阶段,是允许用户线程和垃圾线程同时一起允许的,固内存中必须要留一部分供用户线程使用,具体预留可由参数控制,在并发清除阶段,若用户线程产生的大对象,导致预留的内存地址不足以放下大对象,则CMS会产生并发失败,暂停STW,采用serial old来对老年代重新进行标记-整理。

内存碎片问题和空间预留问题带来的是停顿时间不可预估,固CMS升级为G1,G1的优点是设置最大的停顿时间,回收收益最大的Region区域。

这里有一个小概念Mod union table,周志明的书上好像没有谈及到这个知识点,我们说到记忆集(卡表),目的是为了在GC时避免扫描整个老年代的对象,而记忆集中记录了老年代中哪些对象指向新生代的引用,范围大大缩小了,而Mod union table其实就是卡表(记忆集)的备份。gc后都会对记忆集进行重置,而Mod union table保证了备份。

G1垃圾收集器
概念
图片

    遵循分代理论,但弱化分代概念,引出分区概念,也有新生代和老年代,但内存空间都是由Region区域相同大小组成,G1可以设置最大的垃圾回收停顿时间,优先收集回收垃圾效益最大的Region区域,简称:哪个Region中的垃圾多,优先回收该Region区域的垃圾,然后把非垃圾对象移动到新的Region中。

默认G1有2048个Region区域,若一个Region区域可以分配1MB的对象,若存在一个大对象占用2MB,则由多个Region区域组成连续的humongous区域来存储大对象。

当申请的对象大于一个Region区域的一半时,可以理解成大对象,这种对象默认是会放在Old的Region区中。

步骤
初始标记
STW,标记GC roots直接关联的对象,同时在每一个Region区域中定义了TAMS的指针,可以理解是Region中的一块空间,保证垃圾线程和用户线程并发标记时,用户线程新分配的对象都处于该TAMS指定的空间内,确保这些新对象此次不纳入回收范围。
并发标记
用户线程和垃圾回收线程同时执行,此阶段也会产生多标和漏标问题,G1选用原始快照来记录灰色对象删除白色对象引用的快照,待最终标记时,重新扫描快照的各个引用。
最终标记
STW,重新扫描原始快照中的引用链。
最终清除
STW,根据设置的最大垃圾回收停顿时间,并在这个阶段统计回收益最大的Region区域,将这些region区域作为回收集,优先回收收益最大的回收集,同时将回收集中存活的对象,都移动到新的Region中,再清理掉垃圾对象。
使用
适用于整个堆空间,不像CMS只能收集old区
每个Region区域都有一个记忆集,用来记录跨代引用的引用链。
G1的好处是采用了分而治之的思想,G1将堆空间分了很多个Region区域,可以保证每个小的Region区域的回收时间可控,而CMS是扫描整个老年代,在老年代内做标记-清除。
注意:如果垃圾回收的速度比不上对象分配的速度,就会导致Full GC,所以并不是最大垃圾回收时间设置得越小越好,要合理。

缺点
鸡蛋里找骨头,多此一举,缺点在shenandoah垃圾收集器中解决。

CMS和G1的区别
1:CMS只针对于老年代回收,G1针对于整个堆空间回收,多了一个Mixed GC:整个堆空间(无论老年代还是新生代)中回收垃圾效益最高的Region区。
2:CMS采用增量更新,G1采用原始快照,
3:CMS采用标记-清除算法,G1采用标记-整理算法。
4:CMS只有一个记忆集用来存储跨代引用,而G1中每一个Region区域都存在记忆集。
5:CMS会带来内存碎片和空间预留问题,而G1不会,这个主要也是跟回收算法理论有关。

Shenandoah
概念
可以理解为G1的升级版,如果强行给G1找缺点,其实也能找到两个
1:G1中的每个Region都存在一个记忆集,记忆集也是占用内存资源的。
2:G1中的最终清除是会STW,用户线程和垃圾线程不能同时允许。
shenandoah也是以Region区为最小单位,用一个链接矩阵的方式维护一个记忆集,同时也支持并发清除。

步骤
初始标记
标记所有跟GC roots直接关联的对象
并发标记
用户线程和垃圾线程同时允许,垃圾线程扫描其他引用对象,若存在漏标,则使用原始快照记录,在最终标记中重新扫描记录中的对象。
最终标记
重新扫描原始快照中的记录,同时将回收效益最大的Region们组成回收集。这点和G1不同的,G1是在最终标记时,统计回收效益的会收集,而shenandoah是在最终标记时统计的。
并发清除
主要是清除一个存活对象都没有的Region区域。
并发回收
用户线程和垃圾线程同时允许,根据最大回收时间,回收效益最大的Region区域,然后将存活的对象移动到新的Region区域中去。当用户线程引用的对象,从旧的内存地址移动到新的Region区域去时,shenandoah采用了转发指针的形式,保证原本指向原旧地址的引用对象,转发到新Region地址上。
初始引用更新
确保回收的Region区域中的存活对象都移动到新的Region区域中。
并发引用更新
逐渐去除转发指针的指向,真正更新引用,将堆中指向旧region地址的对象,都更新指向新的region地址,将原地址改成新地址即可。
最终引用更新
修改gc root中的引用
并发清除
主要是清除一个存活对象都没有的Region区域。
其实只要掌握并发标记,并发回收,并发引用更新三个步骤即可,主要是新增了一个转发指针的概念,同时支持回收阶段用户线程和垃圾线程同时运行。

ZGC
概念
每个Region区域并非都相等大小,ZGC中的Region支持动态调整。小Region为2M,中Region为32M,大Region为动态变化,必须是2的整数倍,用来存储大对象,每个大Region只会存储一个大对象。

记住一个小概念,ZGC具备染色指针,染色指针的作用是,当某一个Region区域中存活的对象都移到新Region区域后,该Region区域就能被回收和重用掉,而shenandoah却需要在并发引用更新阶段,确保所有指向该Region区域的引用都修正后,才尝试去回收该Region区域。

步骤
初始标记
ZGC的标记不是在对象上,而是在指针上,标记阶段会更新染色指针上的Marked 0 ,Marked 1标识位。
并发预备重分配
扫描所有的Region区域,组成分配集,目的是为了将分配集中的存活对象都移动到新的Region区域中,同时清除垃圾对象
回收集:是G1和Shenandoah独有的,目的是为了根据最大垃圾回收时间,回收效益最大的Region区域。

分配集:目的是为了减少跨代引用,用扫描更大的成本省去G1中记忆集的维护成本。

并发重分配
把分配集中的存活对象都移动到新的Region中,同时维护一个转发表,因为用户线程仍可允许生成新的对象,如有引用是指向原region地址的,则转发到新的region地址上,同时修改原有的region的地址值为新的地址值,即只要转发一次,顺便就改了新地址值。
这也是染色指针的好处,而shenandoah的转发指针转发次数是未知的。

并发重映射
此阶段不重要,主要是处理跟shenandoah中并发引用更新一样的事。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值