Java最新【JVM系列5】深入分析Java垃圾收集算法和常用垃圾收集器,mysql原理面试

惊喜

最后还准备了一套上面资料对应的面试题(有答案哦)和面试时的高频面试算法题(如果面试准备时间不够,那么集中把这些算法题做完即可,命中率高达85%+)

image.png

image.png

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

垃圾收集算法

===================================================================

上面分析了如何确定一个对象属于可回收对象的两种算法,那么当一个对象被确定为垃圾之后,就需要对其进行回收,回收也有不同的算法,下面就来看一下常用的垃圾收集算法

标记-清除(Mark-Sweep)算法


标记-清除算法主要分为两步,标记(Mark)和清除(Sweep)。

比如说有下面一块内存区域(白色-未使用,灰色-无引用,蓝色-有引用):

在这里插入图片描述

然后标记-清除算法会进行如下两个步骤:

  • 1、将堆内存扫描一遍,然后会把灰色的区域(无引用对象,可悲回收)对象标记一下。

  • 2、继续扫描,扫描的同时将被标记的对象进行统一回收。

标记清除之后得到如下图所示:

在这里插入图片描述

可以很明显看到,回收之后内存空间是不连续的,产生了大量的内存空间碎片。过多内存碎片最直接的就是可以导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

标记-清除算法的缺点

1、标记和清除两个过程都比较耗时,效率不高

2、会产生大量不连续的内存碎片。

为了解决这两个问题,所以就有了复制算法。

复制(Copying)算法


复制算法的思想就是把内存区域一分为二,两块内存保持一样的大小,每次只使用其中的一块,当其中一块内存使用完了之后,将仍然存活的对象复制到另一块内存区域,然后把已使用的一半内存全部一次性清理掉。

如下图(绿色表示暂时不放对象的一半空间):

在这里插入图片描述

回收之后:

在这里插入图片描述

复制算法的缺点

复制算法的缺点就是牺牲了一半的内存空间,有点过于浪费。

复制算法在Java虚拟机的落地形式

Java堆内存中做了好几次划分,最后是将Survivor区分成了2个区域S0和S1来进行复制算法,这种做法就是为了弥补原始复制算法直接将一半的空间作为空闲空间方式的弥补。想要详细了解Java堆内存划分及原因的可以点击这里

IBM公司的研究表明,Young区(新生代)中98%的对象都是“朝生夕死”的,生命周期极短,所以说在一次GC之后能存活下来的对象很少,完全没必要划分一半的区间来进行复制算法。Hot Spot虚拟机中Eden区和Survivor区域的比例为:Eden:S0:S1=8:1:1,也就是说其实只有10%的空间被浪费掉,完全是可以接受的。

标记-整理(Mark-Compact)算法


我们想一下,假如Young区(新生代)的对象在一次GC之后,基本所有对象都存活下来了,那就需要复制大量的对象,效率也会变低。而堆中的old区(老年代)的特点就是对象生命周期极为顽强,因为默认要进行第16次垃圾回收的时候还能存活下来的对象才会放到老年代,所以对老年代中对象的回收一般不会选择标记-复制算法。

标记-整理算法就是为了老年代而设计的一种算法,标记-整理算法和标记清除算法的区别就是在最后一步,标记-整理算法不会对对象进行清理,而是进行移动,将存活的对象全部向一端移动,然后清理掉端边界以外的对象。如下图所示:

回收前:

在这里插入图片描述

回收后:

在这里插入图片描述

分代收集算法(Generational Collection)


目前主流的商业虚拟机都是采用的分代收集算法,这种算法本质上就是上面介绍的算法的结合体。新生代采用标记-清除算法,老年代采用标记-清除或者标记-整理算法。

垃圾收集器

==================================================================

上面介绍了确定对象的算法以及回收对象的算法,然后具体要怎么落地却并没有一个规定,而垃圾收集器就是实现了对算法的落地,而因为落地形式不同,自然也产生了很多不同的收集器。下面是一张收集器的汇总图:

在这里插入图片描述

上面一半表示新生代收集器,下面一半表示老年代收集器,横跨中间的表示都可以用。

根据这个图形有了整体认知之后,我们再来一个个看看这些垃圾收集器的工作原理吧。

Serial和Serial Old收集器


Serial收集器是基本、发展历史悠久的收集器,在JDK1.3.1之前是虚拟机新生代收集的唯 一选择。

Serial收集器是一种单线程收集器,而且是在进行垃圾收集的时候需要暂停所有其他线程,也就是说触发了GC的时候,用户线程是暂停的,如果GC时间过长,用户是可以明显感知到卡顿的。

Serial Old是Serial的一个老年代版本,也是一种单线程收集器。

可以用下面一个图形来表示一下Serial和Serial Old收集器的工作原理:

在这里插入图片描述

优点:简单高效,拥有很高的单线程收集效率

缺点:收集过程需要暂停所有线程

算法:Serial采用复制算法,Serial Old采用标记-整理算法

适用范围:Serial用于新生代,Serial Old用于老年代

应用:Client模式下的默认的收集器

ParNew收集器


ParNew收集器是Serial收集器的多线程版本,实现了并行执行,其余工作原理都和Serial一致。可以使用参数:-XX:+UseParNewGC来指定使用。

注意:这里的并行指的是多个GC线程并行,但是其他线程还是暂停,而并发指的是用户线程和GC线程同时执行。

ParNew收集器默认开启和CPU个数相同的线程数来进行回收,可以使用参数:-XX:ParallelGCThreads来限制线程数

ParNew收集器工作原理如下图:

在这里插入图片描述

优点:在多CPU时,比Serial效率高。

缺点:收集过程暂停所有应用程序线程,单CPU时比Serial效率差

算法:复制算法

适用范围:新生代

应用:运行在Server模式下的虚拟机中首选的新生代收集器

Parallel Scavenge收集器


Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,和ParNew一样也是一个并行的多线程收集器,Parallel Scanvenge收集器相比较于ParNew收集器,更关注与提升系统的吞吐量。

吞吐量指的是CPU用于运行用户代码的而时间于CPU总消耗时间的比值。

即:吞吐量=运行用户代码时间/(运行用户代码时间+GC时间)

Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量:

-XX:MaxGCPauseMillis//GC最大停顿毫秒数,必须大于0

-XX:GCTimeRatio//设置吞吐量大小,大于0小于100,默认值为99

我们思考一个问题,假如我们通过参数把允许最大停顿毫秒数设置的相对较小会怎么样?是不是GC速度就会变快了

答案是否定的。如果设置的时间过短,Parallel Scavenge收集器会牺牲吞吐量和新生代空间来交换。

比如新生代400Mb需要GC时间为100ms,然后手动设置为50ms,那么就会把新生代调小为200Mb,这样肯定时间就降下来了,然而这种操作可能会降低吞吐量,假如说原先是10s触发一次GC,每次100ms,修改时间后编程5s触发一次GC,每次70ms,那么10s触发两次GC时间就变成了140ms,吞吐量反而降低。

如果不知道如何设置,那么还可以通过参数:-XX:+UseAdaptiveSizePolicy开启自适应策略(GC Ergonomics),这样我们就不需要手动设置吞吐量和GC停顿时间了,虚拟机会根据运行情况手机监控信息来动态调整。

Paralled Old收集器


Paralled Old收集器是Parallel Scavenge收集器的老年代版本,但是这个收集器是jdk1.6之后才出现的,所以导致了在Paralled Old收集器出现之前Parallel Scavenge收集器一直找不到合适的“搭档”。因为Parallel Scavenge收集器没办法和CMS收集器配合使用(后面会介绍原因),所以在Paralled Old收集器出现之前,如果新生代选择了Parallel Scavenge收集器,那么老年代就只能选择Serial Old收集器,而Serial Old收集器是单线程的,所以单单只是新生代替换成了多线程的吞吐量收集器Parallel Scavenge,在性能上并不一定有多少提升。

在注重吞吐量的业务系统中,可以考虑Parallel Scavenge+Paralled Old收集器配合使用,结合使用后的工作原理如下图所示:

在这里插入图片描述

PS:在jdk1.8中,默认收集器就是Parallel Scavenge+Parallel Old组合

CMS(Concurrent Mark Sweep)收集器


这是一种以实现GC时最短停顿时间为目标的收集器,也是一款真正实现了并发回收的收集器。当然,虽然是并发的,但是仍然需要Stop The World,只是尽可能将这个时间缩到最短。

对于任何暂停时间要求较低的应用程序,都应该考虑使用此收集器。CMS收集器可以通过参数:-XX:+UseConcMarkSweepGC启用。

CMS收集器是基于算法标记-清除来实现的,整个过程分为4步:

  • 1、初始标记(inital mark)

需要Stop The World。标记GC Roots对象,因为GC Root对象并不会很多,所以这个过程非常快。

  • 2、并发标记(concurrent mark)

这个阶段可以和用户线程同时进行,也可以分为三步:

(1)并发标记(CMS-concurrent-mark):主要是进行GC Roots Tracing。就是说根据第1步中找到的GC Root对象,开始搜索,这个过程相比阶段1是比较慢的。

(2)预清理(CMS-concurrent-preclean),这个阶段是为了处理并发标记之后发生了变化的对象

(3)可被终止的预清理(CMS-concurrent-abortable-preclean),这个预清理差不多,但是是可以被终止的,主要是分了尽可能分担下面第3步的工作,这个阶段会有一个abort触发条件,该阶段存在的目的是希望能发生一次Young GC,这样就可以减少Young区对象的数量,降低重新标记的工作量,因为重新标记会扫描整个堆内空间。可以通过参数-XX:+CMSScavengeBeforeRemark参数控制在重新标记前发生一次Young GC,默认为false。这个阶段发生的最大时间由-XX:CMSMaxAbortablePrecleanTime控制,默认5s

  • 3、重新标记(remark)

需要Stop The World,这个阶段是为了修正在阶段2标记之后产生了变化的对象

  • 4、并发清除(concurrent sweep)

和用户线程同时进行,开始正式清除垃圾,在此阶段也会产生垃圾,产生垃圾后无法清除,只能留待下一次GC。

CMS收集过程如下图所示:

在这里插入图片描述

CMS优缺点

  • 优点:并发收集、低停顿。

其实最主要的是CMS把收集过程中步骤拆分了,而最耗时的操作都是并发执行,自然就会低停顿了。

  • 缺点:产生大量空间碎片、并发阶段会降低吞吐量。

CMS采用的是标记-清除算法,所以会产生大量的空间碎片。在阶段2和阶段4并发执行的时候,会占用CPU资源,就会导致应用程序变慢,降低了吞吐量。

Floating Garbage(浮动垃圾)

上面的步骤中,步骤2是并发标记,所以在标记过程中,可能会有新的垃圾产生而没有被标记到。比如说对象A,刚扫描的时候是有效对象,然后继续扫描的时候,对象A又变成不可用了,然后还有并发清除的阶段,也可能会有新的垃圾产生,这种就称之为浮动垃圾(Floating Garbage)。CMS并不能收集浮动垃圾,只能等到下一次GC时再回收。

Concurrent Mode Failure(并发模式失败)

CMS收集器不能和其他收集器一样等到空间满了才开始触发GC,因为CMS收集的时候是并发的,并发的过程肯定会持续产生对象,如果因为在垃圾收集期间内存不足而导致了GC失败,就称之为Concurrent Mode Failure。出现这种情况之后,Java虚拟机就会启动预备方案,启用Serial Old收集器替换CMS收集器,这时候整个GC过程都会Stop The World。

CMS收集器的触发阈值可以通过参数:-XX:CMSInitiatingOccupancyFraction=来进行设置,N为(0,100)之间,在jdk1.6中默认是92,即老年代空间使用率达到92%就会触发CMS收集器开始进行垃圾回收。

G1(Garbage-First)收集器


G1也是以实现GC时最短停顿时间为目标并发回收的收集器,它尝试以高概率满足垃圾收集(GC)暂停时间目标,同时实现高吞吐量。

在G1之前的其他收集器都是属于分代收集器,也就是说一个收集器要不然用于新生代,要不然就是用于老年代,而G1中,将堆的整个内存布局做了很大的修改,在G1中,将整个Java堆划分为多个大小相等的独立区域(Region),虽然在逻辑上还保留了新生代和老年代的概念,但是物理上已经没有隔离了。

G1收集器中堆内布局如下图所示:

在这里插入图片描述

上图中堆被划分为一组大小相同的Region,每个Region都是连续的虚拟内存范围。

G1可以知道哪个Region区域内大部分都是空的,这样就可以在每次允许的收集时间内去优先回收价值最大的Region区域(根据回收所获得的空间大小以及回收所需要的时间综合考虑),所以这也就是G1为什么叫做Garbage-First的原因。

PS:G1是JDK1.9的默认垃圾收集器

G1特点

经过上面的简单介绍,可以得出G1主要有以下特点:

  • 1、实现了并行与并发,尽可能的缩短了Stop The World时间。

  • 2、分代收集:逻辑上依然保留了分代概念

  • 3、空间整合:整体来看是基于“标记-整理”算法来实现的(如果冲Region来看,是基于“复制”算法),所以不会产生大量内存空间碎片。

  • 4、支持可预测的停顿时间:可以通过参数来设置每次GC最大时间

  • 5、非实时收集:因为可以人为设置停顿时间,所以在指定时间范围内会进行优先选择收集,而不会收集所有被标记好的垃圾。

G1工作流程

G1收集器在工作流程上和CMS比较相似,只是在最后的步骤有所区别,主要经过了如下4个步骤:

  • 1、初始标记(Initial Marking):需要Stop The World。标记一下GC Roots能够关联的对象,并且修改TAMS(Next Top at Mark Start)的值,使得下一阶段并发运行时,能在正确可用的Region中创建对象。

  • 2、并发标记(Concurrent Marking):和CMS一样,主要是进行GC Roots Tracing,找出存活对象进行标记。

  • 3、最终标记(Final Marking):需要Stop The World。和CMS一样,这个阶段主要是为了修正在并发标记期间因用户程序继续运行而导致产生变动的对象。

  • 4、筛选回收(Live Data Counting and Evacuation):对各个Region的回收价值和成本进行排序,根据 用户所期望的GC停顿时间制定回收计划进行回收。

分享

这次面试我也做了一些总结,确实还有很多要学的东西。相关面试题也做了整理,可以分享给大家,了解一下面试真题,想进大厂的或者想跳槽的小伙伴不妨好好利用时间来学习。学习的脚步一定不能停止!

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

Spring Cloud实战

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

Spring Boot实战

薪酬缩水,“裸辞”奋战25天三面美团,交叉面却被吊打,我太难了

面试题整理(性能优化+微服务+并发编程+开源框架+分布式)

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

好好利用时间来学习。学习的脚步一定不能停止!

[外链图片转存中…(img-Rtkh67jm-1715418153072)]

Spring Cloud实战

[外链图片转存中…(img-C4uSyegC-1715418153072)]

Spring Boot实战

[外链图片转存中…(img-yVd0gxsZ-1715418153072)]

面试题整理(性能优化+微服务+并发编程+开源框架+分布式)

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值