首页 > 其他分享 >细数6种垃圾回收器的区别, 快进来看看有没有你要用的

细数6种垃圾回收器的区别, 快进来看看有没有你要用的

时间:2022-10-11 23:44:40浏览次数:53  
标签:细数 标记 快进来 Region 收集器 回收 XX 垃圾

前言

大家好啊, 这里是Yve菌, 今天给大家详细介绍一下我们在进行GC垃圾回收时的主力军-垃圾回收器. 由于我们使用的jdk版本有不同, 使用的堆内存大小也不同, 这个时候我们可能就会用到不同的垃圾回收器. 垃圾回收器目前来说在市面上会有很多, 今天我们就来详细讲讲以下垃圾收集器: Serial, Parallel Scavenge, Parnew, CMS, G1, ZGC.
常用垃圾回收器


一、垃圾回收算法

在介绍垃圾回收器之前先普及一下垃圾回收算法, 每个垃圾回收器都在进行垃圾回收时都会用到一定的垃圾回收算法, 垃圾回收算法有四种

1.复制算法

在复制算法中, 堆内存空间会被分成两块, 一块用来存储对象, 另一块不使用. 当需要进行垃圾回收时, 垃圾回收器会将存活的对象通过复制的方法到另一块没有使用的区域中, 之后直接将之前使用的区域进行整体清除.
复制算法

2.标记-清除算法

标记清除算法会将存活的对象进行标记, 在进行gc时直接清除掉标记的对象.

这是最基础的收集算法, 但是很显然也带来了一个问题: 内存碎片.
标记清除算法

3. 标记-整理算法

在标记整理算法中也同样会对存活的对象进行标记, 而与标记-清除不同的是, 标记整理算法会将所有存活对象移动到一端, 然后直接清理掉端边界以外的区域.
标记整理算法

4.分代算法

分代算法是一个概念, 即以分代为界限, 根据不同的分代情况使用不同的分代算法. 通常来说年轻代使用复制算法, 老年代使用标记清除或者标记整理算法.

二、Serial收集器

Serial收集器是最基本、历史最悠久的垃圾收集器了。他只采用了一个线程来进行gc并且在gc时其他所有的用户线程都会停止(STW), 效率相对来说非常低.

Serial分为年轻代版本(Serial)和老年代版本(Serial Old), 年轻代使用参数:-XX:+UseSerialGC 老年代则使用参数: -XX:+UseSerialOldGC. Serial老年代版本也是CMS和G1的后备方案.

Serial的年轻代采用复制算法, 老年代采用标记整理算法.
Serial回收流程

三、Parallel Scavenge收集器

Parallel Scavenge可以理解为Serial收集器的多线程版本, 他在进行垃圾回收时会使用多个线程, 效率相比Serial会高很多, 默认的收集线程数跟cpu核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。

Parallel同样也分为年轻代版本(Parallel Scavenge)和老年代版本(Parallel Old), 使用参数 -XX:+UseParallelGC-XX:+UseParallelOldGC激活相应版本.

Parallel年轻代使用复制算法, 老年代使用标记整理算法.
Parallel回收流程

四、ParNew收集器

ParNew和Parallel收集器基本相同, 主要是可以和CMS配合使用. 它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收集器,后面会介绍到)配合工作。

ParNew使用参数: -XX:+UseParNewGC, 年轻代使用复制算法, 老年代使用标记整理算法.
Parnew回收流程

五、CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用,它是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

1. GC流程

我们从名字中的Mark Sweep可以得知, CMS使用的是标记-清除算法. 它的垃圾回收流程比之前的垃圾收集器更复杂, 分为五个步骤.

  1. 初始标记(STW)
    在这个阶段中, 暂停所有其他的线程(STW), 垃圾回收器会先扫描GCRoots的直接引用, 速度很快
  2. 并发标记
    在这个阶段, 其他线程恢复执行, 同时垃圾回收器会继续遍历扫描已经被标记的GCRoots的子引用们, 期间用户线程可能会产生新的垃圾
    这是GC流程中最长的一段时间, 但是由于用户线程和GC线程同时运行, 期间不会发生STW.
  3. 重新标记(STW)
    这个阶段主要是为了解决在并发标记中产生的新的变动以及漏标等问题. 这个阶段也会STW, 时间可能会比初始标记稍长, 但是远小于并发标记. 主要用到了三色标记中的增量更新算法
  4. 并发清理
    其他线程继续运行, gc线程同步对未标记的区域进行垃圾回收.
  5. 并发重置
    将之前进行的标记符号进行重置

CMS回收流程

2. CMS的优缺点

优点:

  • 相对于Parallel Scavenge收集器来说, CMS更注重于用户的体验, 他将STW的时间尽量的缩到最小, 虽然整个垃圾处理流程加起来的总时间可能会较长, 但是用户的体验比较好.

缺点:

  • 对CPU资源敏感(可能会和其他线程抢占资源)
  • 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了)
  • CMS采用标记-清除算法, 这样会造成内存碎片, 我们可以通过参数-XX:+UseCMSCompactAtFullCollection让jvm在执行完清除之后自己整理
  • 由于用户线程和gc线程并发, 可能会导致垃圾还没清理完, 然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入STW,用serial old垃圾收集器来回收

3. CMS参数

  • -XX:+UseConcMarkSweepGC:启用cms
  • -XX:ConcGCThreads:并发的GC线程数
  • -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  • -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
  • -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  • -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  • -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段
  • -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  • -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

六、G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.

1. 内存布局

在G1中内存布局发生了一些变化, 他并未使用之前的明确分代概念, 而改为了将Java堆划分为多个大小相等的独立区(Region). JVM目标是不超过2048个Region(JVM源码里TARGET_REGION_NUMBER 定义),实际可以超过该值,但是不推荐。

一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M,当然也可以用参数-XX:G1HeapRegionSize手动指定Region大小,但是推荐默认的计算方式。

默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存,对应大概是100个Region,可以通过-XX:G1NewSizePercent设置新生代初始占比,在系统运行中,JVM会不停的给年轻代增加更多的Region,但是最多新生代的占比不会超过60%,可以通过-XX:G1MaxNewSizePercent调整。年轻代中的Eden和Survivor对应的region也跟之前一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个,s0对应100个,s1对应100个。

Region是可以动态变化的, 当一个Region没被占用时, 他既可以存放老年代对象, 也可以存放年轻代对象.

G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象的处理,G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2M,只要一个大对象超过了1M,就会被放入Humongous中,而且一个大对象如果太大,可能会横跨多个Region来存放。

Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销。
Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。

G1内存布局

2. GC流程

  1. 初始标记(initial mark,STW)
    与CMS基本相同, 暂停所有其他的线程(STW), 将GCRoots的直接引用进行标记, 速度很快
  2. 并发标记(Concurrent Marking)
    与CMS基本相同, 其他线程恢复执行, 同时垃圾回收器会继续遍历扫描已经被标记的GCRoots的子引用们, 期间用户线程可能会产生新的垃圾
  3. 最终标记(Remark,STW)
    与CMS的重新标记相同, 主要是为了解决在并发标记中产生的新的变动以及漏标等问题.这个阶段也会STW, 时间可能会比初始标记稍长, 但是远小于并发标记.
  4. 筛选回收(Cleanup,STW)
    这是G1特有的阶段, 筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿STW时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划,比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集合),尽量把GC导致的停顿时间控制在我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。G1的回收算法是复制算法,将一个region中的存活对象复制到另一个region中,因此回收后几乎不会有太多内存碎片

G1回收流程

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来),比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率。

3. 特点

  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
  • 空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段(通过参数-XX:MaxGCPauseMillis指定)内完成垃圾收集。

4. 垃圾收集分类

  • YoungGC
    与其他垃圾收集器的YoungGC不同的是, 当G1的Region中的年轻代对象满了之后, 他会先进行一次检测, 如果回收Eden区的对象预计时间远远小于设定参数 -XX:MaxGCPauseMills时, 不会立刻进行回收, 而是会使用新的Region来继续存放年轻代对象
    , 直到回收年轻代对象的预计时间接近设定值才会触发YoungGC.

  • Mixed GC
    老年代的堆占有率达到参数-XX:InitiatingHeapOccupancyPercent时触发, 回收所有年轻代的对象, 以及部分老年代的对象(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区. 过程采用复制算法, 当Region里对象复制时没有足够的Region存放会触发FullGC

  • FullGC
    停止系统程序,然后采用单线程(Serial)进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的

5. 参数

  • -XX:+UseG1GC:使用G1收集器
  • -XX:ParallelGCThreads:指定GC工作的线程数量
  • -XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
  • -XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
  • -XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%,值配置整数,默认就是百分比)
  • -XX:G1MaxNewSizePercent:新生代内存最大空间
  • -XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
  • -XX:MaxTenuringThreshold:最大年龄阈值(默认15)
  • -XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了
  • -XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。
  • -XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
  • -XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。

6. 适用场景

  • 50%以上的堆被存活对象占用
  • 对象分配和晋升的速度变化非常大
  • 垃圾回收时间特别长,超过1秒
  • 8GB以上的堆内存(建议值)
  • 停顿时间是500ms以内

七、ZGC

ZGC(The Z Garbage Collector)是JDK 11中推出的一款追求极致低延迟的垃圾收集器,它曾经设计目标包括:

  • 停顿时间不超过10ms(JDK16已经达到不超过1ms);
  • 停顿时间不会随着堆的大小,或者活跃对象的大小而增加;
  • 支持8MB~4TB级别的堆,JDK15后已经可以支持16TB。

1. 内存布局

ZGC彻底抛弃了堆内存中的分代概念, 而是采用了基于Region的内存布局.
Region具有小, 中, 大三类容量:

  • 小型Region(Small Region): 容量固定大小为2M, 用于存放小于256KB的小对象
  • 中型Region(Medium Region): 容量固定为32M, 用于存放256KB-4M之间的对象
  • 大型Region(Large Region): 容量不固定, 但是必须为2M的倍数, 用来存放4M及4M以上的大对象, 并且一个大Region只能存一个大对象.
    ZGC内存布局

2. NUMA-aware

NUMA对应的有UMA,UMA即Uniform Memory Access Architecture,NUMA就是Non Uniform Memory Access Architecture。UMA表示内存只有一块,所有CPU都去访问这一块内存,那么就会存在竞争问题(争夺内存总线访问权),有竞争就会有锁,有锁效率就会受到影响,而且CPU核心数越多,竞争就越激烈。NUMA的话每个CPU对应有一块内存,且这块内存在主板上离这个CPU是最近的,每个CPU优先访问这块内存,那效率自然就提高了
NUMA

3. 回收流程

ZGC回收流程

ZGC的回收流程可以分为两个大阶段:

  1. 标记阶段
    总体来说标记阶段与之前的垃圾回收器相同, 但是区别是, 之前的垃圾回收器是会在对象的对象头中标记, 而ZGC则是会更新颜色指针中的Mark0Mark1
    1.1 初始标记(STW): 同CMS, G1. 暂停所有其他的线程(STW), 将GCRoots的直接引用进行标记, 速度很快
    1.2 并发标记: CMS, G1, 其他线程恢复执行, 同时垃圾回收器会继续遍历扫描已经被标记的GCRoots的子引用们, 期间用户线程可能会产生新的垃圾
    1.3 再标记(STW): 与CMS的重新标记相同, 主要是为了解决在并发标记中产生的新的变动以及漏标等问题.这个阶段也会STW, 时间可能会比初始标记稍长, 但是远小于并发标记.

  2. 转移阶段
    2.1 并发转移准备: 在这个阶段, ZGC会分析出哪些Region需要清理和转移
    2.2 初始转移(STW): 暂停其他线程, 转移之前分析出的初始对象, 并直接进行重映射
    2.3 并发转移: 恢复其他线程, 同时转移初始对象的子引用, 并维护一个转发表来记录这些对象转移之后的位置.

重映射
在转移阶段中我们通过维护一张转发表来记录对象转移之后的位置, 但是其他对象指向的地址未改变还是原引用, 对象在访问原引用之前会通过读屏障读取转发表中信息找到对象转移后的位置. 在进行下一次gc标记之前, 会将之前转发表中记录的对象进行重映射, 也就是把原对象的位置改为新的位置, 之后把转发表中数据清空.

4. 颜色指针

Colored Pointers,即颜色指针,如下图所示,ZGC的核心设计之一。以前的垃圾回收器的GC信息都保存在对象头中,而ZGC的GC信息保存在指针中。
颜色指针
每个对象有一个64位指针,这64位被分为:

  • 18位:预留给以后使用;
  • 1位:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问;
  • 1位:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set表示需要GC的Region集合);
  • 1位:Marked1标识;
  • 1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;
  • 42位:对象的地址(所以它可以支持2^42=4T内存)

5. ZGC存在的问题

ZGC最大的问题是浮动垃圾。ZGC的停顿时间是在10ms以下,但是ZGC的执行时间还是远远大于这个时间的。假如ZGC全过程需要执行10分钟,在这个期间由于对象分配速率很高,将创建大量的新对象,这些对象很难进入当次GC,所以只能在下次GC的时候进行回收,这些只能等到下次GC才能回收的对象就是浮动垃圾。

6. 参数

ZGC参数设置
启用ZGC比较简单,设置JVM参数即可:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC。调优也并不难,因为ZGC调优参数并不多,远不像CMS那么复杂。它和G1一样,可以调优的参数都比较少,大部分工作JVM能很好的自动完成。下图所示是ZGC可以调优的参数:
ZGC参数


总结

我们了解到了这么多垃圾收集器, 那么我们怎么去选择使用呢?

  • 优先调整堆的大小让服务器自己来选择
  • 如果内存小于100M,使用串行收集器
  • 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
  • 如果允许停顿时间超过1秒,选择并行或者JVM自己选
  • 如果响应时间最重要,并且不能超过1秒,使用并发收集器
  • 4G以下可以用parallel,4-8G可以用ParNew+CMS,8G以上可以用G1,几百G以上用ZGC

以上就是本期的内容啦, 如果能帮到你麻烦点个赞呗! 感谢大家!

标签:细数,标记,快进来,Region,收集器,回收,XX,垃圾
From: https://www.cnblogs.com/Limelimelimes/p/16783036.html

相关文章