超详细!jvm垃圾收集器学习笔记

1 垃圾收集器分类

  • 线程数分,可以分为串行垃圾回收器和并行垃圾回收器

  • 串行模式是指在一个时间段内只有允许有一个cpu用于垃圾回收,适用于单核cpu

  • 并行模式可以运用多个cpu同时执行垃圾回收

  • 按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器

    • 并发式垃圾回收器与应用程序线程交替工作,减少应用程序的停顿时间

    • 独占式垃圾回收器一旦运行就会停止用户线程

  • 碎片化处理方式,分为压缩式垃圾回收器和非压缩式垃圾回收器

    • 压缩式垃圾回收器,会对存活对象进行压缩整理,消除回收后碎片 (分配对象空间就可以使用指针碰撞方式)

    • 非压缩式不进行操作(分配对象空间使用:空闲列表方式)

  • 工作区间可以分为年轻代垃圾回收器与老年代垃圾回收器

2 评估gc的性能指标

  • **吞吐量:**运行用户代码的时间占运行时间的比例

  • **暂停时间:**执行垃圾收集时,程序工作线程被暂停的时间。

  • 内存占用: java堆区所占的内存大小

一款优秀的收集器最多同时满足其中的两项。主要抓住两点:吞吐量与暂停时间

吞吐量:

  • 吞吐量 = 运行用户代码时间/运行用户代码时间+垃圾收集时间

  • 吞吐量优先,意味着在单位时间内,stw的时间最短

暂停时间:

  • 暂停时间优先意味着单次stw的时间最短

  • 高吞吐量与低暂停时间是矛盾的
    • 如果以吞吐量优先,就需要降低内存回收的执行频率,导致gc需要更长的暂停时间来执行回收。

    • 如果以低延迟优先,只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。

现在的标准:在最大吞吐量的优先的情况下,降低停顿时间

3 垃圾收集器的分类

  • 串行回收器:serial,serial old

  • 并行收集器:parnew,parallel scavenge ,parallel old

  • 并发回收器:cms ,g1

3.1 垃圾收集器与垃圾分代之间的关系

  • 新生代收集器:serial,parnew,parallel scavenge

  • 老年代收集器:serial old,parallel old,cms

  • 整堆收集器:g1

组合关系

我们选择的只是对具体应用最合适的收集器

4 查看默认的垃圾回收器

  • 使用参数
-XX:+PrintCommandLineFlags
  • 在命令行使用命令
jps 查看pid

jinfo -flag [参数] pid 查看相应参数信息(+:使用 -:未使用)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TnrmNUrv-1624607566960)(https://pic.imgdb.cn/item/60d3fca8844ef46bb2123222.jpg)]

5 Serial 回收器:串行回收

  • serial 收集器采用复制算法,串行回收,和stw机制执行内存回收。

  • 除了年轻代,serial收集器还提供于执行老年代垃圾收集器 serial old 收集器。serial old 收集器同样也采用了串行回收和 stw机制不过回收算法四标记 - 压缩算法。

    • serial old 是在client模式下的默认的老年代垃圾回收器

    • 用途:① 与新生代parallel scavenge 配合使用,② 作为老年代cms收集器的后别垃圾收集

此收集器是单线程,只会使用一个cpu或一条收集线程完成垃圾收集工作,在进行垃圾收集时,必须暂停其它所有的工作线程

使用:

通过参数-XX:+UseSerialGc参数可以指定年轻代和老年代都使用串行收集器。

总结:

此收集器在单核cpu可以用,对于交互较强的应用,这种垃圾收集器是不能接收的。

6 parnew回收器:并行回收

  • parnew 收集器是serial收集器的多线程版本。

  • parnew采用的是并行回收的方式执行内存回收,在新生代也是采用复制算法stw机制

  • 对于是新生代,回收次数频繁,使用并行方式高效

  • 对于老年代,回收次数少,使用串行方式节省资源(cpu并行需要切换线程)

使用:

-XX:+UseParNewGc 来设置parnew收集器执行内存回收任务
-XX:ParallelGCThreads 限制线程数量,默认和cpu相同

7 parallel回收器:并行且吞吐量优先

  • parallel 收集器同样采用了复制算法,并行回收和stw

与parnew的区别:

  • 和parallel收集器的目标是达到一个可控制的吞吐量

  • 自适应调节策略也是parallel与parnew的一个重要区别

  • 高吞吐量主要适合在后台运算而不需要太多交互的任务,如:批量处理,订单处理

  • parallel old 采用的是标记-压缩算法,也是基于并行回收的

  • 在吞吐量优先的情况下,parallel收集器和parallel old 收集器的组合,在server模式下的内存回收性能

java8中默认是此垃圾收集器

使用:

  • -XX:+UseParallelGC手动指定年轻代使用parallel并行收集器

  • -XX:+UseParallelOldGC手动指定老年代使用并行回收器parallel

    • 分别适用于新生代和老年代。默认jdk8是开启的

    • 上面的两个参数默认开启一个,另一个也会被开启(互相激活)

  • -XX:ParallelGCThreads设置年轻代并行收集器的线程数。

    • 默认情况下 ,当cpu数量小于8,此值等于cpu数量。

    • 当cpu数量大于8 此值为 3+【5*cpu_count/8】

  • -XX:+UseAdaptiveSizePolicy设置 parallel 收集器具有自适应调节策略。

    • 这种模式下,年轻代的大小,eden和survicor的比例,晋升老年代的对象年龄参数会被自动调整。

8 cms回收器:低延迟

  • jdk 1.5 后,推出cms,是jvm中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集器线程与用户线程同时工作

  • cms的关注点在于尽可能缩短垃圾收集时用户线程的停顿时间。

  • 所采用标记-清除算法

8.1 cms工作原理

整个过程主要分为四个阶段:初始标记,并发标记,重新标记,并发清除。

  • 初始标记:会出现stw 在这个阶段的主要任务仅仅只是标记处gc roots能直接关联到的对象

  • 并发标记:从gc root的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程

  • 重新标记 : 为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录(出现stw)

  • 并发清除:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间

8.2 三色标记

三色标记法将对象的颜色分为了黑、灰、白,三种颜色。

  • 黑色:该对象已经被标记过了,且该对象下的属性也全部都被标记过了。(程序所需要的对象)
  • 灰色:该对象已经被标记过了,但该对象下的属性没有全被标记完。(GC需要从此对象中去寻找垃圾)
  • 白色:该对象没有被标记过。(对象垃圾)

算法流程:

从我们main方法的根对象(JVM中称为GC Root)开始沿着他们的对象向下查找,用黑灰白的规则,标记出所有跟GC Root相连接的对象
扫描一遍结束后,一般需要进行一次短暂的STW(Stop The World),再次进行扫描,此时因为黑色对象的属性都也已经被标记过了,所以只需找出灰色对象并顺着继续往下标记(且因为大部分的标记工作已经在第一次并发的时候发生了,所以灰色对象数量会很少,标记时间也会短很多)
此时程序继续执行,GC线程扫描所有的内存,找出被标记为白色的对象(垃圾)清除

8.3 cms 特点

  • 由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收时低停顿的

  • 由于用户线程没有中断 ,在cms回收中,还要确保应用程序用户线程有足够的内存可用 因此cms收集器,不会等到老年代填满时在进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收。

cms优点

  • 并发收集
  • 低延迟

cms弊端:

  • 产生内存碎片。
  • cms收集器对cpu资源非常敏感,总吞吐量会下降
  • cms收集器无法处理浮动垃圾:在并发标记阶段产生的新垃圾,cms无法对这些垃圾进行标记,会导致这些垃圾无法被回收。

8.4 cms 为什么不使用标记压缩算法

  • 由于当执行并发清除时,要保证原来的用户线程能执行,它的运行资源要不受此影响。所以要采用标记清除算法。

8.5 使用cms

  • -XX:+UseConcMarkSweepGC手动指定使用cms,开启后会将
    -XX:+UseParNewGC打开。即 parnew(新生区) +cms(老年区)的组合

8.6 小结

  • 最小化使用内存和并行开销:serial gc+serialold gc

  • 最大化应用程序的吞吐量:parallel gc + parallelOld gc

  • 最小化gc中断或停顿时间:parnew gc + cms

jdk9将cms设置为遗弃的,jdk14将其移除

9 g1收集器:区域分代化

9.1 g1 由来

为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间。

官方给g1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起’全功能收集器’的任务

  • g1主要针对配备多核cpu及大容量内存的机器,以及高概率满足gc停顿时间,还兼具高吞吐量

  • 是jdk9以后默认垃圾回收器。被称为全功能垃圾回收器

9.2 g1 特点

  • 并行与并发

    • 并行性:g1在回收期间,可以有多个gc线程同时工作,有效利用多核计算能力。

    • 并发性:g1拥有与应用程序交替执行的能力

  • 分代收集

  • 从分代上看,g1依然属于分代型垃圾回收器。但从堆的结构来看,它不要求整个eden区,年轻代或者老年代是连续的

  • 将对空间分为若干个区域(region),这些区域包含了逻辑上的年轻代和老年代

  • 同时兼顾年轻代和老年代

  • 空间整和

    • cms:“标记清除算法”,内存碎片,若干次gc后进行一次碎片处理

    • g1 将内存划分为region,内存回收是以region为基本单位。region之间是复制算法,但整体上是标记-压缩算法。

  • 可预测的停顿时间模型

    这是g1对于cms的一大优势,能让使用者明确在一个长度为m毫秒的时间片段内,消耗在垃圾收集的时间上不超过n毫秒

    • 由于分区的原因,g1可以只选取部分区域进行内存回收,缩小了回收范围。

    • g1 跟踪各个region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的region,保证了g1收集器在有限的时间可以获取尽可能高的收集效率

9.3 g1 参数设置

-XX:+UseG1GC 手动开启g1(jdk9之后默认)

-XX:G1HeapRegionSize 设置每个Region的大小,值是2的幂

-XX:MaxGCPauseMillis 设置期望达到最大gc停顿时间的指标默认为200

9.4 设计原则

是为了简化jvm性能调优,开发人员一般只需要简单三步。

第一步:开启g1垃圾收集器

第二步:设置堆的最大内存

第三步:设置最大的停顿时间

g1 提供了三种垃圾回收模式:young gc,mixed gc 和full gc 在不同条件被触发

9.5 分区Region : 化整为零

  • 在使用g1收集器时,它将java堆划分为2048个大小相同的独立Region块,每个region块大小根据堆空间大小而定,可以通过-XX:G1HeapRegionSize设置。所有的region大小相同。

  • 一个region有可能属于 eden,survivor或者old/trnured内存区域,但是只属于一个角色

  • g1 增加了 Humongous 内存区域,主要用于存储大对象,超过1.5个region,就放到h。


设置h的原因

用于存储短期存在的大对象。

9.6 g1回收器过程

  • 年轻代gc(young gc)

  • 老年代并发标记过程

  • 混合回收

  • (如果需要,单线程,独占式,高强度的full gc 还是继续存在,是对gc评估失败提供了失败保护机制)

9.6.2 主要回收环节

  • 当年轻代的eden区用尽时开始年轻代回收过程,g1的年轻代收集阶段是一个并行的独占式的收集器,g1 gc 暂停所有的应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到survicor区间或者是老年区

  • 当堆内存达到一定值(默认45%),开始老年代并发标记

  • 标记完成开始混合回收,g1,gc移动存活对象到空闲区间,这些空闲区间称为老年代一部分。与其他老年代回收不同,g1的老年代不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代region就行

9.6.3 g1 回收器过程:Remember set

问题:

一个region不可能是孤立的,一个region对象可能被其他region对象引用,是否需要扫描整个Java堆才能正确?回收新生代也不得不同时扫描老年代?

解决方法:

无论g1 还是其他分代收集器,jvm都使用remember set来避免全局扫描。

  • 每个region都有一个对应的Remembered set

  • 每次引用类型数据写操作时,都会产生 write barrier 暂时中断操作

  • 然后检查要写入的引用指向的对象是否和该 Referce类型数据在不同的Region(其他收集器:检查老年代对象是否引用了新生代对象)

  • 如果不同就把相关信息记录到所引用指向对象所在Region对应的remembered set中。

  • 在进行垃圾回收时,在gc根节点的范围加入remembered set 。

9.6.4 年轻代gc

当eden的内存空间耗尽时,会开始回收eden区和survivor区。

  • 第一阶段,扫描跟

扫描gc root(局部变量,静态变量所指向的对象,常量池以及根引用rset-记忆集)

  • 第二阶段 更新rset

更新rset,完成后rset可以准确的反映老年代对所在的内存分段中对象的引用

  • 第三阶段 处理rset

识别被老年代对象指向的eden中的对象,这些被指向的eden中的对象被认为是存活对象

  • 第四阶段,复制对象

eden区内存段中存活对象被复制到survivor区中空内存分段,如果survivor区内存分段存活对象年龄未达阈值,年龄加1,达到阈值会被复制到old区中空的内存分段。如果survivor空间不够,则eden空间部分数据直接晋升到老年代空间

  • 第五阶段,处理引用

处理soft,weak,final等引用。最终eden空间的数据为空,gc停止控制。

9.6.5 并发标记过程
  1. 初始标记阶段

根节点标记直接可达对象,这个阶段是stw并且会触发 年轻代gc

  1. 根区域扫描

g1 gc扫描survivor区中可以直接可达的老年代区域对象,并标记被引用对象。

  1. 并发标记

在整个堆中进行并发标记,若发现区域对象中所有对象都是垃圾,那这个区域会被立即回收,在标记过程,会计算区域的对象活性

  1. 再次标记

由于程序持续进行,需要修正上一次结果

  1. 独占清理

计算各个区域存活对象和gc回收比例,进行排序

  1. 并发清理阶段

识别并清理完全空闲的区域

9.6.6 混合回收
  • 当越来越多晋升到老年代old region时,会触发混合垃圾收集器,除了回收整个young region 还会回收old region,这里需要注意,是一部分老年代,而不是全部老年代
9.6.7 g1 回收可选过程full gc

导致g1Full gc 的原因可能有两个:

  • evacuation的时候没有足够的to-space来存放晋升的队象。

  • 并发处理过程完成之前空间耗尽。

10 七种垃圾收集器总结

怎样选择垃圾收集器

  • 优先调整堆的大小让jvm自适应完成

  • 如果内存小于100m,使用串行收集器

  • 如果单核且没有停顿时间要求,串行收集器

  • 多cpu,需要高吞吐量,允许停顿时间超一秒,选择并行(parrlel)

  • 如果是多cpu,追求低停顿时间,使用并发收集器
    官方推荐g1,性能高

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值