JVM-垃圾收集器

1 篇文章 0 订阅
本文详细介绍了Java的垃圾收集器,包括Serial、ParNew、ParallelScavenge、SerialOld、ParallelOld、CMS和G1。CMS收集器追求最短停顿时间,而G1能够预测停顿时间。文章还讨论了三色标记法及其解决的漏标问题,以及CMS和G1中应对浮动垃圾的不同策略。
摘要由CSDN通过智能技术生成

垃圾收集器

1 简介

在这里插入图片描述

如上图,一共有7种作用于不同分代的垃圾收集器,如果两个收集器之间存在连线,则说明它们可以搭配使用,垃圾收集器所处区域表示它是属于新生代收集器还是老年代收集器;

  • 新生代收集器:SerialParNewParallel Scavenge
  • 老年代收集器:CMSSerial OldParallel Old
  • 整堆收集器:G1

2 详情

2.1 Serial收集器

新生代收集器,最早的收集器,单线程的,收集时需暂停用户线程的工作(STW),所以有卡顿现象。但是实现简单,不会有线程切换的开销。

参数:-XX:+UseSerialGC

新生代采用复制算法,老年代采用标记+整理算法,GC线程执行时会暂停所有用户线程

在这里插入图片描述

2.2 ParNew收集器

新生代收集器,就是Serial收集器的多线程版本,但是单CPU下,ParNew还需要切换线程,可能还不如Serial。采用复制算法

Serial 和ParNew收集器可以配合 CMS收集器,前者收集新生代,后者CMS收集老年代,

参数:

  • -XX:+UseConcMarkSweepGC:指定使用 CMS 后,会默认使用 ParNew 作为新生代垃圾收集器;
  • -XX:+UseParNewGC:强制指定使用ParNew
  • -XX:ParallelGCThreads=2:指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU 的数量相同;
    在这里插入图片描述

2.3 Parallel Scavenge收集器

简称Parallel ,新生代收集器,基于复制算法,并行的多线程收集器(与ParNew收集器类似),侧重于达到一个可控的吞吐量,虚拟机运行100分钟,垃圾收集花1分钟,则吞吐量为99%,有时候我们也把该垃圾收集器叫吞吐量垃圾收集器或者是吞吐量优先的垃圾收集器;是JVM默认的新生代的垃圾收集器。有自适应调节策略Parallel Scavenge 垃圾收集器中的Ergonomics负责自动的调节gc暂停时间和吞吐量之间的平衡,自动优化虚拟机的性能;

参数:

  • -XX:MaxGCPauseMillis :该参数设置大于0的毫秒数,每次GC的时间将尽量保持不超过设置的值,但是这个值也不是设置得越小就越好,GC暂停时间越短,那么GC的次数会变得更频繁;

  • -XX:+UseParallelGC: 指定使用Parallel Scavenge 垃圾收集器

2.4 Serial Old收集器

它是Serial收集器的老年代版本,同Serial一样,单线程,可在Client模式下使用,也可在Server模式下使用,采用标记-整理算法,Serial Old收集器也可以作为CMS收集器发生失败时的后备预案,在并发收集发生Concurrent Mode Failure时使用;

在这里插入图片描述

2.5 Parallel Old收集器

Parallel Old 是Parallel Scavenge 的老年代版本,多线程,标记整理算法,它是在jdk1.6开始才提供;在注重吞吐量和CPU资源的情况下,Parallel Scavenge新生代+ Parallel Old老年代是一个很好的搭配。采用标记-整理算法

参数: -XX:+UseParallelOldGC:指定使用Parallel Old收集器。
在这里插入图片描述

2.6 CMS收集器

CMS 全称 Concurrent Mark Sweep,是一款老年代的垃圾收集器,它是追求最短回收停顿时间为目标的收集器,互联网B/S结构的服务器端特别适合此收集器;

以前学过的一些垃圾回收会带来Stop the World(stw)的问题,会导致系统卡死时间过长,很多响应无法处理,所以CMS垃圾回收器采取的是垃圾回收线程和系统工作线程尽量同时执行的模式来处理的,基于标记-清除算法

参数:-XX:+UseConcMarkSweepGC:指定使用CMS垃圾收集器

2.6.1 回收过程
  1. 初始标记

    STW,此过程中会标记GC Roots能直接关联到的对象,即可存活的对象;速度很快。

  2. 并发标记

    不会STW追踪GC Roots的整个链路,从GC Roots的直接关联对象开始遍历整个对象引用链路,这个过程耗时较长,但是不需要暂停用户线程,可以与垃圾回收线程一起并发运行。

  3. 重新标记

    STW修正在并发标记期间因为用户程序继续运行而导致标记产生变化的那一部分对象的标记记录,这个阶段的时间通常会比初始标记稍微长一些,但是远比并发标记阶段的时间短,它其实就是对在第二阶段中被系统程序运行变动过的少数对象进行标记,所以速度很快。

  4. 并发清除

    不会STW清理删除掉标记阶段判断的已经死亡的对象很耗时,不需要移动存活对象(标记-清除算法),这个阶段也是与用户线程并发执行的。

其中初始标记和重新标记需要暂停用户线程(Stop The World),其它阶段都是并发执行,所以总体上暂停时间更短;

2.6.2 缺点
  1. 并发收集会占用CPU资源,特别是cpu数量小的服务器下,会占用用户线程,导致性能下降,CMS默认启动的回收线程数是(处理器核心数量+3)/4
  2. 产生浮动垃圾,因为并发清除的时候用户线程可能还在产生垃圾,这些垃圾没有清除,而且不能让老年代填满了再清除,要给用户线程留一定空间,所以jdk1.5默认是老年代68%了就触发回收,jdk1.6则提升到了92%。
  3. CMS基于标记-清除算法,清理后会产生碎片空间,空间碎片过多时,将会导致大对象无法分配,往往会出现老年代还有很多剩余空间,但没有足够大的连续空间来分配当前对象,而不得不提前触发一次Full GC。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eiDNB8Br-1655190506625)(C:\Users\13998\AppData\Roaming\Typora\typora-user-images\image-20220612214513209.png)]

2.7 G1收集器

G1全称Garbage First,G1垃圾回收器可以同时回收新生代和老年代,不需要两个垃圾回收器配合起来使用;JDK 9发布时,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则被声明为不推荐使用(Deprecate)的收集器。新生代基于复制算法

2.7.1 基本原理

G1是一款可以让我们设置垃圾回收的预期停顿时间的垃圾收集器,设置参数是-XX:MaxGCPauseMillis,默认值是200ms; 其实我们对内存合理分配,优化jvm参数,就是为了尽可能减少新生代(Minor GC),或者是整个老年代(Major GC),或者是整个Java堆(Full GC),尽量减少GC带来的系统停顿,避免影响系统处理请求,G1可以指定垃圾回收导致的系统停顿时间不能超过多久,不管垃圾的多与少,垃圾回收的时间都不要超过我们设置的值(并不是绝对的),G1全权给你负责,保证达到这个目标,这相当于我们就可以直接控制垃圾回收对系统性能的影响了;

所以G1垃圾收集器是尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象,这就是G1垃圾收集器的核心原理;

2.7.2 G1垃圾收集器如何做到可预测的停顿时间?
  1. G1收集器最大的特点就是把整个JAVA堆内存拆分成多个大小相等的Region,在回收的时候可以指定Region,回收粒度更小。

在这里插入图片描述

  1. G1会追踪每个Region的回收价值,计算每个Region里的对象有多少是垃圾,如果对这个Region进行垃圾回收,需要耗费多长时间,可以回收掉多少垃圾。

  2. G1收集器之所以能建立可预测的停顿时间模型,是因为它将Region作为垃圾回收的最小单元,即每次可以选择一部分Region进行收集,避免在整个Java堆中进行全区域的垃圾收集,让G1收集器去跟踪各个Region里面的垃圾的“回收价值”,然后根据用户设定的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定,默认值是200毫秒),然后在后台维护一个优先级列表,优先处理回收价值大的那些Region,这也是“Garbage First”名字的由来,这种使用Region划分堆内存空间,基于回收价值的回收方式,保证了G1收集器在有限的时间内尽可能收集更多的垃圾;

    比如:G1通过追踪发现,1个Region中的垃圾对象有10MB,回收它需要耗费500毫秒,另一个Region中的垃圾对象有20MB,回收它需要耗费100毫秒,那么G1垃圾收集器基于回收价值计算会选择回收20MB只需要100毫秒的Region;

2.7.3 大对象

Region中有一类特殊的Humongous区域,专门用来存储大对象;

G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象,每个Region的大小可以通过参数-XX:G1HeapRegionSize设定,取值范围为1MB~32MB,而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中,G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待;

2.7.4 新生代垃圾回收

G1的新生代也有Eden和Survivor,其触发垃圾回收的机制也是类似的,随着不停在新生代Eden对应的Region中放对象,JVM就会不停的给新生代加入更多的Region,直到新生代占据堆大小的最大比例60%,之后采用复制算法进行垃圾回收。

但这个过程与之前是有区别的,因为G1是可以设定目标GC停顿时间的,也就是G1执行GC的时候最多可以让系统停顿多长时间,可以通过-XX:MaxGCPauseMills参数来设定,默认值是200ms,那么G1就会通过对每个Region追踪回收它需要多少时间,可以回收多少对象来选择回收一部分Region,保证GC停顿时间控制在指定范围内,尽可能多地回收对象;

2.7.5 老年代垃圾回收

回收过程:

  1. 初始标记

    需要Stop the World,不过仅仅标记一下GC Roots直接能引用的对象,这个过程速度很快,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿;

  2. 并发标记

    不需要Stop the World,这个阶段会从GC Roots开始追踪所有的存活对象,初始标记阶段仅仅只是标记GC Roots直接关联的对象,而在并发标记阶段,就会进行GC Roots追踪,从这个GC Root对象直接关联的对象开始往下追踪,追踪全部的存活对象,这个阶段是很耗时的,但可以和系统程序并发运行,所以对系统程序的影响不大;

  3. 重新标记(最终标记)

    需要Stop the World,用户程序停止运行,最终标记一下有哪些存活对象,有哪些是垃圾对象;

  4. 筛选回收

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

    从整体上看,G1垃圾收集像是一种标记-整理算法,它不存在内存碎片问题,实际上它是一种复制算法,G1收集器除了并发标记外,其余阶段也是要完全暂停用户线程的, 所以它并不是纯粹地追求低延迟,而是给它设定暂停目标,使其在延迟可控的情况下获得尽可能高的吞吐量;

3 三色标记

在三色标记法之前有一个算法叫Mark-And-Sweep(标记清除)。这个算法会设置一个标志位来记录对象是否被使用。最开始所有的标记位都是0,如果发现对象是可达的就会置为1,一步步下去就会呈现一个类似树状的结果。等标记的步骤完成后,会将未被标记的对象统一清理,再次把所有的标记位设置成0方便下次清理。

这个算法最大的问题是GC执行期间需要把整个程序完全暂停,不能实现用户线程和GC线程并发执行。因为在不同阶段标记清扫法的标志位0和1有不同的含义,那么新增的对象无论标记为什么都有可能意外删除这个对象。对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决GC运行时程序长时间挂起的问题,那就是三色标记法。

三色标记最大的好处是可以异步执行,从而可以以中断时间极少的代价或者完全没有中断来进行整个GC。

三色标记法很简单。首先将对象用三种颜色表示,分别是白色、灰色和黑色。

  • 黑色:表示根对象,或者该对象与它引用的对象都已经被扫描过了。
  • 灰色:该对象本身已经被标记,但是它引用的对象还没有扫描完。
  • 白色:未被扫描的对象,如果扫描完所有对象之后,最终为白色的为不可达对象,也就是垃圾对象。

在这里插入图片描述

3.1 漏标问题

在这里插入图片描述

  1. 假设此时,对象A及其引用的对象都已经被扫描完,那么对象A将会被标记为黑色。
  2. 用户线程将对象B和对象C之间的引用断开,将对象A指向对象C,此时对象C会被当成垃圾对象,会产生漏标问题,因为对象A不会再被扫描。
3.1.1 漏标问题解决方案

漏标问题在CMSG1收集器中有着不同的解决方案。

  • CMS - 增量更新:采用IncrementalUpdate(增量更新)算法,在并发标记阶段时如果一个白色对象重新被一个黑色对象引用时,就通过写屏障将这个引用关系记录下来,会将黑色对象重新标记为灰色,让垃圾收集器在重新标记阶段以黑色对象为根重新扫描,确保不会漏标。
  • G1 - 原始快照:原始快照是站在减少引用的对象的角度来解决问题。采用SATB(snapshot-at-the-beginning),在初始标记时做一个快照,当B和C之间的引用要删除时,就将该引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象设为根,重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑色。

两种漏标解决方案的对比:

  • SATB算法关注的是引用的删除(B->C的引用)。
  • Incremental Update算法关注的是引用的增加(A->C 的引用),需要重新扫描,效率低。
3.1.2 为什么CMS中使用增量更新,G1中使用原始快照?

SATB相对增量更新效率会高,因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

3.2 多标-浮动垃圾

在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过(被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“ 浮动垃圾 ”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。

内容来源于参考:https://www.bilibili.com/video/BV12b4y167Mb?p=83&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=b7169ede41b87272daf64ca9e2cd6c43
https://blog.csdn.net/b416055728/article/details/121499128

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值