当前位置:   article > 正文

JVM的各种垃圾收集器详解(CMS、G1)_jvm垃圾回收器cms g1

jvm垃圾回收器cms g1

我们可以把垃圾回收器分为3类

串行、吞吐量优先、响应时间优先

其中,串行是单线程,吞吐量优先和响应时间优先是多线程

本文重点讨论的是分代收集器,ZGC会另外做一个文章专门讲解

1 串行(单线程)

Serial收集器(复制算法)——新生代

Serial Old收集器(标记-整理算法)——老年代

  • 单线程
  • 对内存较小,适合个人电脑
  • 采用复制算法-新生代,标记整理算法-老年代

-XX:+UseSerialGC=Serial+SerialOld——开启串行GC

回收过程

在这里插入图片描述

安全点:

GC时,所有用户线程都会在一个地方停止下来,这个地方就是安全点,目的是防止用户线程引用的内存地址因垃圾回收而混乱

2 吞吐量优先(多线程)

Parallel Scavenge收集器(标记-复制算法)——新生代

Parallel Old收集器(标记-复制算法)——老年代

追求高吞吐量,高效利用CPU(垃圾回收时CPU占用会到100%),吞吐量一般为99%,吞吐量=用户线程时间/(用户线程时间+GC线程时间)

  • 多线程
  • 堆内存较大,多核cpu
  • 让单位时间内,STW的时间最短 0.2 0.2 = 0.4
  • 采用复制算法-新生代,标记整理算法-老年代
  • 垃圾回收时间对总程序的时间占比越低,则吞吐量越高

开启使用

-XX:+UseParallelGC — -XX:+UseParallelOldGC——开启吞吐量优先GC,开启其中一个,另一个自动开启(JDK1.8默认开启)

-XX:+UseAdaptiveSizePolicy——采用自适应的新生代大小调整

-XX:GCTimeRatio=ratio——调整吞吐量的目标,垃圾回收时间不能超过总时间的(1/(1+ratio)),ratio默认为99,一般设置为19,如果达不到设定的吞吐量,就会把堆内存设置得更大,这样垃圾回收的次数就不频繁,但单次GC暂停时间就会越长

-XX:MaxGCPauseMillis=ms——调整最大暂停时间,默认200ms,与GCTimeRatio冲突

-XX:ParallelGCThreads=n——设置线程数

回收过程

在这里插入图片描述

3 响应时间优先(多线程)

ParNew收集器(标记-复制算法)——新生代,Serial收集器的多线程版本

CMS(Concurrent Mark Sweep)收集器(标记-清除算法)——老年代

用户线程与GC线程并发进行,用户线程与GC线程会竞争抢占CPU

  • 多线程
  • 堆内存较大,多核cpu
  • 尽可能让单次STW的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5
  • 在垃圾回收的过程中可能会产生新垃圾,被称为浮动垃圾
  • 采用标记清除算法-老年代,复制算法-新生代

开启使用

-XX:+UseConcMarkSweepGC — -XX:+UseParNewGC — SerialOld——可以与用户线程并发执行,用于老年代,在并发开启失败后(由于内存碎片过多),会退化为串行

-XX:ParallelGCThreads=n — -XX:ConcGCThreads=threads——ParallelGCThreads是并行的线程数,ConcGCThreads是并发的线程数,一般设置为ParallelGCThreads的1/4

-XX:CMSInitiatingOccupancyFraction=percent——为了应对浮动垃圾,预先设置触发GC的内存阈值,一般设置为80,早期JDK默认设置为65

-XX:+CMSScavengeBeforeRemark——在重新标记之前,要对新生代进行垃圾回收

回收过程

在这里插入图片描述

初始标记: 仅仅标记GC ROOTS的直接关联对象,即只标记根对象,世界暂停

并发标记: 使用GC ROOTS TRACING算法,进行跟踪标记,世界不暂停

重新标记: 因为之前并发标记,其他用户线程不暂停,可能产生了新垃圾,所以重新标记,世界暂停,且只追踪在并发标记过程中产生变动的对象

问题

  1. 会产生浮动垃圾
  2. 会产生过多的内存碎片,导致CMS退化为串行,响应时间增加

4 G1

概述

定义: Garbage First(Garbage One),一种新型垃圾收集器,相比于CMS收集器,G1收集器有两个最突出的改进

  • 基于 标记-整理 算法,不会产生内存碎片
  • 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收

发展历史

  • 2004 论文发布
  • 2009 JDK6 体验
  • 2012 JDK7 官方支持
  • 2017 JDK9 默认

适用场景

  • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms
  • 超大堆内存,堆内存越大,G1对比CMS的优势就越大,会将堆划分为多个大小相等的 Region(每个区域1,2,4,8…,每个区域都可以作为Eden、Servivor、old…)
  • 整体上是 标记+整理 算法(解决了CMS的内存碎片问题),两个区域之间是 复制 算法

相关JVM参数

-XX:+useG1GC——JDK9以前

-XX:G1HeapRegionSize=size

-XX:MaxGCPauseMillis=time

G1 垃圾回收阶段

在这里插入图片描述

新生代垃圾收集、新生代垃圾收集+并发标记、混合收集三个阶段,循环发生

第一个阶段——Young Collection(新生代)

每个region可以作为任意区域,新生成的对象会任选一个region放入,该region就会变成Eden区(简称E区)

在这里插入图片描述

E区资源紧张,会触发GC,把依然存活的对象放入一个新的region,该region就会变成Survivor区(简称S区),该过程会STW

在这里插入图片描述

如果S区资源紧张,会触发GC,把可以放入老年代的对象放入一个新的region,该region就会变成Old区(简称O区),该过程会STW

在这里插入图片描述

第二阶段——Young Collection+CM(新生代垃圾回收+并发标记)

  • 在Young GC时会进行GC Root的初始标记

  • 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),该过程与CMS的并发标记类似,阈值由下面的JVM参数决定

    -XX:InitiatingHeapOccupancyPercent=percent——默认45

在这里插入图片描述

第三阶段——Mixed Collection(混合收集)

会对E、S、O区进行全面垃圾回收

  • 最终标记(Remark)会STW,主要标记之前并发标记时漏掉的对象,这个地方也与CMS的重新标记类似

  • 拷贝存活(Evacuation)会STW,对老年代来说,G1 会根据最大暂停时间,部分回收O区的垃圾,只回收垃圾最多的区域,这也是为什么叫Garbage First的原因

    -XX:MaxGCPauseMillis=ms——最大停顿时间

在这里插入图片描述

图中,橘色虚线边框的O区并没有发生GC,就是因为G1会采取Garbage First的策略

所以,G1之所以能做到既保证高吞吐量,又保证低响应时间,就是因为它并不会对老年代中所有的对象进行回收,而是选择性地进行回收,G1之所以能这样做,是基于其分区的策略,即把整个堆划分为了一个一个region。

每种垃圾回收器发生Full GC的时机
  • SerialGC

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足发生的垃圾收集 - full gc
  • ParallelGC

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足发生的垃圾收集 - full gc

  • CMS

    • 新生代内存不足发生的垃圾收集 - minor gc

    • 老年代内存不足,并发失败 - full gc

  • G1

    • 新生代内存不足发生的垃圾收集 - minor gc
    • 老年代内存不足(老年代占整个堆内存的45%时),且垃圾回收速度跟不上用户产生垃圾速度 - full gc
Young Collection 跨代引用

新生代的根对象查找,有一部分来自老年代,老年代存活对象很多,如果挨个遍历,效率会很低,这个时候我们就会采用一种卡表技术(card table)

我们会把O区细分为很多个card(512k),如果老年代中有一个对象引用了新生代的对象,我们就把这个对象对应的card标记为脏卡,这样,新生代的根对象查找时,我们只用遍历脏卡,大大缩小了遍历区域

在这里插入图片描述

  • 卡表与 Remembered Set
  • 在引用变更时,是异步进行的,通过 post-write barrier+dirty card queue 技术(这个地方笔者精力有限,没有深入研究,感兴趣的读者可以自行查阅别的资料),将待更改的card放入一个card queue中进行更新
  • concurrent refinement threads 更新 Remembered Set

在这里插入图片描述

Remark

pre-write barrier——写屏障技术,在对象引用改变前,将对象让如队列satb_mark_queue

在这里插入图片描述

黑色的是已经处理完成的且有引用的对象,灰色的是在处理中的对象,白色的是还没有处理的对象

其它版本的优化

JDK 8u20 字符串去重

在新生代回收时检查是否有重复字符串,如果有,则让他们指向同一个对象

优点:节省大量内存

缺点:略微增加新生代回收时间

-XX:+UseStringDeduplication 默认开启

JDK 8u40 并发标记类卸载

所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它所加载的所有类

-XX:+ClassUnloadingWithConcurrentMark 默认开启

JDK 8u60 回收巨型对象

  • 一个对象大于 region 的一半时,称之为巨型对象
  • G1 不会对巨型对象进行拷贝
  • 回收时被优先考虑
  • G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉

JDK 9 并发标记起始时间的调整

  • 并发标记必须在堆空间占满前完成,否则退化为 FullGC

  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent

  • JDK 9 可以动态调整

    • -XX:InitiatingHeapOccupancyPercent 用来设置初始值

    • 进行数据采样并动态调整

    • 总会添加一个安全的空档空间

JKD 9 更高效的回收

之后的优化这里就不一一列出了,感兴趣的读者可以去查阅Oracle官方文档:https://docs.oracle.com/en/java/javase/12/gctuning

以上便是JVM中垃圾收集器的全部内容

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/561971
推荐阅读
相关标签
  

闽ICP备14008679号