JVM 部分收集器(Serial、CMS、G1)

回收算法可以看作是内存回收的方法论,而垃圾收集器就是内存回收的实践论。
有七种垃圾收集器,
在这里插入图片描述
收集器是可以配合使用的,如图实现所连接的收集器是的可以配合使用的,图中有一条线,上半部分是作用域新生代的,而下半部分是作用与老年代的。

1.Serial收集器在这里插入图片描述

这个是再1.3.1之前新生代收集器的唯一选择,这是一个单线程的工作处理器,这个线程最大的问题就是,当它开始垃圾回收时,必须暂停其他所有工作的线程。直到该线程工作完毕, 暂停其他工作线程,一般称为 stop the world, 简称为STW。 当然这个收集器并不是一无是处,好处有所占的内存开销最小,与其他线程没有交互,可以获得最大的单线程收集效率。甚至是客户端模式下的默认收集器。

2.CMS (Concurrent Mark Sweep)

CMS 是一种目标以最短停顿时间为目标的收集器
基于标记-清除算法实现的,整个过程分为四个步骤
在这里插入图片描述

  1. 初始标记(CMS initial mark) // 需要 stop the world
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark) // 需要stop the world
  4. 并发清除(CMS concurrent sweep)
2.1初始标记 (STW)

初始标记阶段,会Stop the word , 这个阶段CMS仅标记被GC Root直接引用的对象,所以速度很快。
为什么这个阶段要STW呢?
试想一下,如果初始标记阶段 GC线程和用户线程同时运行,GC的过程中,用户线程还是会有新的垃圾对象产生 ,那啥时候能标记完,徒增复杂。 同时CMS设计的理念就是用户感知至上,所以虽然STW,但也尽量缩短STW的时间,所以选择了仅标记被GC Root直接引用的对象,而无需遍历整个堆 。

2.2 并发标记 (用户线程和GC线程并行工作)

经过了上一步的初始标记, 已经将GC Root 直接引用的对象标记完成。
CMS 老年代的Garbage Collector , 回收的是整个堆的垃圾对象,效率随着堆的大小成反比 ( 如果堆比较大,比如10G,CMS也是有点吃力的,所以才有了G1)

为了避免GC时间过长,这个阶段CMS 让用户线程和 GC线程同时工作,尽量减少用户线程的停顿。 GC线程这个时候就要从GC Roots的直接关联对象开始遍历整个对象图,这个耗时最长。 所以CMS重点优化了这块 。 I

用户线程和GC线程并行工作,多少都会存在一些问题 。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。

试想一下 GC工作的时候,用户线程也在工作

  1. 如果GC完成后,当时遍历的用户线程引用的对象由不是垃圾对象,变成了垃圾对象 ,那是不是就 漏标了?
  2. 如果GC线程运行中,当时标记的对象是垃圾对象,但是用户线程运行的过程中又把这个对象重新引用了,那是不是 错标了 ?
    怎么办呢? CMS设计了 下个阶段 重新标记 来修复这个阶段因对象状态变更导致的标记错误。
2.3重新标记 (STW)

这个阶段主要是修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。 所以这个时候要STW,不然还是会出现阶段二的情况。

这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短

如何重新标记呢? 这里只要采用 三色标记中的增量更新算法。

2.4并发清理(用户线程和GC线程并行工作)

又是并行执行

如果这个清理阶段又有新的对象进来(肯定没有被标记,因为上一步已经标记过了),这个时候怎么办? 也删掉? 还不是垃圾对象啊 删掉那肯定不行。该怎么办呢?

CMS是这样处理的: 对于新增对象会被标记为黑色不做任何处理

这样的对象被称为 【浮动垃圾】, 标记黑色,本次不处理,等下次GC。

CMS的优缺点
优点

  1. 并发收集
  2. 低停顿 ,用户体验至上

缺点:

  1. 用户线程和GC线程有可能争抢CPU资源
  2. 无法处理浮动垃圾 , 在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了
  3. 回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生, 可以通过参数-XX:+UseCMSCompactAtFullCollection让jvm在执行挖完标记清除后再做整理,从而实现“标记-整理”的效果,减少内存碎片。 还有个参数 -XX:CMSFullGCsBeforeCompaction 代表多少次Full GC以后整理一下内存碎片,默认为0 即每次Full GC之后都会整理内存碎片。
  4. 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况 。 比如在并发标记和并发清理阶段会出现concurrent mode failure,这个时候会STW,会切换到Serial Old ,效率非常低。

3.5 concurrent mode failure 是怎么回事儿

本身full GC 就是因为老年代没有空间了, 在 并发标记 和 并发清除阶段 是 用户线程和GC线程 并行执行, 如果这个时候 用户线程又产生了一些大对象或者符合条件的对象晋升到了老年代, 这个时候 老年代没有空间存放这些对象了,GC一边回收,系统一边运行,也许没回收完就再次触发full gc , 就出现了 “concurrent mode failure” .

出现这个情况,CMS是怎么处理呢? 直接OOM?

显然不是,CMS会Stop the word , 然后使用Serial Old 单线程 来进行垃圾回收。 尽量避免这种情况,效率非常低。

如何避免呢? 可以合理设置CMS的参数 (-XX:CMSInitiatingOccupancyFraction) 默认92% ,可以将这个值设置为80%(根据业务考量该值,你的系统大对象多的话 ,当然了,你设置了 80% 就意味着你老年代将会有20%的空间不可用,这部分空间仅能用来存放GC过程中新的对象,需要合理评估),尽量避免并发失败的情况的发生。

3.G1(Garbage First)

jdk9的默认收集器;
设计思想是:延迟可控的情况下获得最大的吞吐量, 停顿时间模型(Pause Prediction Model)的收集器, 意思是能指定在M毫秒的一个时间片内,希望不要超过n毫秒的一个设定,比如:在5分钟内,停顿时间最好不要超过3秒。

G1 与之前的收集器有很大的不同,并且也更复杂, G1内存划分

G1看起来和CMS比较类似,但是实现上有很大的不同。

传统分代GC将整体内存分为几个大的区域,比如Eden,S0,S1,Tenured等。

而G1将内存区域分为了n个「不连续的」,「大小相同」的Region,Region具体的大小为1到32M,根据总的内存大小而定,目标是数量不超过2048个。 如下图所示:
在这里插入图片描述
每个Region在G1中扮演了不同的角色,比如Eden(新生区),比如Survivor(幸存区),或者Old(老年代)

除了传统的老年代,新生代,G1还划分出了Humongous区域,用来存放巨大对象(humongous object,H-obj)。

对于巨大对象,值得注意的有以下几点:

H-obj的定义是大于等于Region一半的对象
H-obj直接分配到Old gen,防止频繁拷贝。但是H-obj的回收却不是在Mixed GC阶段,而是concurrent marking阶段中的clean up过程和full GC

这点一定注意,在调优过程中你会在GC日志中经常发现这句 [GC pause (G1 Humongous Allocation)
(young) (initial-mark), 0.0029216 secs] 疑惑点就在于为什么Humongous
Allocation却是引发的yong gc。 原因便是在于为了通过yong gc的initial-mark开始进行concurrent
marking,进而通过clean up回收大对象

如果想要查看G1日志的时候,为了方便快速达到GC的效果,你可能会直接分配一些大对象以便填满整个堆从而引发GC,但是如果光是大对象,你可能会发现GC日志中并没有Mixed
GC,而是频繁的Yong GC和Concurrent Marking,这便是原因

H-obj永远不会被移动,虽然G1的回收算法总体上看是基于标记-整理的,但是对于H-obj则永远不会移动,要么直接被回收,要么一直存在。因此H-obj可能会导致较大的内存碎片进而引起频繁的GC

G1的回收过程

G1的内存划分形式,决定了G1同时需要管理新生代和老年代。根据回收区域的不同,G1分为两种回收模式:

只回收部分年轻代的内存:Yong GC
回收所有年轻代内存和部分老年代内存: Mixed GC
❝ 当mixed gc回收速度赶不上内存分配的速度,G1会使用单线程(使用Serial Old的代码)进行Full GC

其中,整个Yong GC过程都回STW

G1按固定大小把内存划分为很多小区域(region),这个堆大概有2000多块;在逻辑上,某些小区域构成Eden,某些构成Survivor,某些构成老年代,这些小区域物理上是不相连的,并且构成新生代和老年代的区域可以动态改变。
可以通过命令行参数-XX:NewRatio=n来配置老年代和新生代的比例,默认为2,即比例为2:1;-XX:SurvivorRatio=n则可以配置Eden与Survivor的比例,默认为8。

Young GC

当Eden区的空间占满之后,会触发Young GC,G1将Eden和Survivor中存活的对象拷贝到Survivor,或者直接晋升到Old Region中。Young GC的执行是多线程并发的,期间会停顿所有的用户线程(STW)。

Old GC

当堆空间的占用率达到一定阈值后会触发Old GC(阈值由命令参数-XX:InitiatingHeapOccupancyPercent设定,默认值45),Old GC分五个步骤完成:

1.初始标记(Initial Mark,STW)

对Survivor区域扫描,标记出可能拥有老年代对象引用的根区域(Root Region),该阶段附属于一次Young GC的最后阶段,所以是STW。在GC日志中会被记录为GC pause (young)(inital-mark)

2.并发标记

从GC Root 开始对堆中对象进行可达性分析,递归扫描整个堆中的对象图, 找出要回收的对象,这一步比较长,但是这一步是并发操作的,

3.最终标记

最一个短暂的暂停,用来处理并发阶段结束后仍然遗留下来的少量SATB记录。

4.筛选回收

负责更新Region的统计数据,堆各个Regin的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个regin构成回收集,然后把决定回收的那一部分Regin存活对象复制到Region中, 再清理掉整个旧Region的全部空间。这里的操作涉及对象的移动,必须暂停用户线程,由多条收集器并发完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值