java 垃圾回收器

垃圾回收器分类与性能指标

概述

垃圾收集器没有在规范中过多的的规定,可以由不同的厂商,不同版本的jvm实现

由于JDK的版本处于高速迭代的过程中,因此java发展至今已经衍生了众多的GC版本

从不同角度分析垃圾收集器,可以将GC分为不同的类型.

分类

按线程分,可以分为串行垃圾收集器并行垃圾收集器

  • 串行回收值得是在同一个时间段内只允许有一个cpu用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作结束.

在诸如cpu单核处理器或者较小引用内存等硬件平台不是特别优越的场合,串行回收器的.所以,串行回收默认被应用在客户端的Client模式下的jvm中

在并发能力比较强的cpu上,并行垃圾回收器产生的停顿时间要短于串行回收器

  • 和串行回收相反,并行收集齐可以运用多个cpu同时执行垃圾回收,因此提升了引用的吞吐量,不过并行回收仍然与串行回收一样,使用了STW的机制.

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

  • 并发式垃圾回收器与应用程序交替工作,已经可能减少应用程序的停顿时间
  • 独占式垃圾回收器,一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束

安碎片处理方式分,可以分为压缩式垃圾回收器和非压缩式垃圾回收器

  • 压缩式垃圾回收期在回收完成后,堆存活对象进行压缩整理,消除回收后的碎片

    • 再分配对象内存空间使用:指针碰撞
  • 非压缩式的垃圾回收期不进行这步操作

    • 再分陪对象空间使用:空间列表

按工作的内存区间划分,又可以分为年轻代垃圾收集齐和老年代垃圾回收器

性能指标

  • 吞吐量:运行用户代码的时间栈运行时间比(总运行时间:程序的运行时间+内存回收时间)
  • 垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
  • 收集频率:相对应用程序的执行,收集操作发生的频率.
  • 内存能用:java堆所占的内存大小
  • 快速:一个对象从诞生到被回收所经历的时间

吞吐,暂停时间和内存占用是核心关注问题

吞吐量

虚拟机运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%

这种情况下,应用程序能容忍较高的暂停时间,因此高吞吐量的应用程序有更长的时间基准,快速响应是不考虑的.

吞吐量有限,因为这在单位时间内,STW的时间最短,

暂停时间

例如GC期间100毫秒的暂停意味着在这100毫秒期间内没有应用程序线程是活动的
暂停时间有限,意味着可让单词stw的时间最短

比较

高吞吐量较好因为这会让应用程序的最终用户感觉只有应用程序在做"生产性"工作,直觉是吞吐量越高程序越快.

地暂停时间(低延迟)较好因为从最终用户的角度来看不管是GC还是其他原因导致一个引用被挂起始终是不好的.这取决于用于程序的类型,有时候甚至200毫秒的打断体验暂停都可以打断用户的体验.因此具有较低较大的暂停时间是非常重要的,特别是对于一个交互式应用程序.

不幸的是"高吞吐量"和"低暂停时间"是一对相互竞争的目标

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

不同的垃圾收集齐器

概述

经典的垃圾收集齐

串行垃圾回收器:Serial,Serial Old

并行垃圾回收器:ParNew,Parallel scavenge,Parallel Old

并发回收器:CMS,G1

关系

新生代收集器:Serial,ParNew,Parallel Scavenge

老年代收集器:Serial Old,Parallel Old,CMS

整堆收集器:G1

组合方式

说明:

Serial Old GC是和CMS GC配合使用的,CMS无法在内存空间满的情况下工作

图中的俩条红线是在jdk1.8中废弃的组合(能用)

绿色的线是jdk14中删除的垃圾回器

青色线代表djk14中删除了cms垃圾回收器


为什么要有那么多垃圾回收期呢?因为java的使用场景很多,移动端,服务端等.所以就需要针对不同场景,提供不同的垃圾回收器,提高垃圾收集的性能.

虽然我们会对各个收集齐进行比较,但并非为了挑选一个最好的收集器出来.我们选择的只是具体应用最合适的垃圾收集器.

查看

-XX:+PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器)

使用命令执行:jinfo -flag 相关垃圾回收期参数 进程id

jdk8中默认,新生代:Parallel Scavenge GC,老年代Parallel Old GC

Serial回收器(串行回收)

serial收集齐是最基本,历史最悠久的垃圾收集齐.jdk1.3之前回收新生代唯一的选择

Serial收集齐作为Hotspot中Client模式下的默认新生代垃圾收集器

Serial收集齐采用复制算法,串行回收和STW机制的方式执行内存回收

除了年轻代之外,Serial收集齐还用于提供用户执行老年代垃圾收集的Serial Old收集器.Serial Old收集齐同样也采用了串行回收和STW机制,只不过内存回收算法是标记压缩算法

Serial Old是运行在Client模式下默认的老年代垃圾回收器

Serial Old在Server模式下主要有俩个用途

  • 与新生代的Parallel Scavenge配合使用
  • 作为老年代CMS收集齐的后背垃圾收集方案

这个收集器是一个单线程的收集齐,但它单线程的意义并不仅仅说明它只会使用一个cpu或一条收集线程区完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的线程,直到他收集结束

优势:

简单而高效(与其他收集器相比),对于限定单个cpu的环境来说,Serial收集齐由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率.

  • 运行在client模式下的虚拟机是个不错的选择

在用户的桌面应用场景中,可用内存一般不大,可以在较短的时间内完成垃圾收集,只要不频繁发生,使用串行垃圾回收器是可以接受的.

在hotspot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都是用串行收集齐

  • 新生代用Serial GC,老年代用Serial Old GC

总结

现在不用串行的了.而且在限定单核cpu才可以使用.现在都不是单核cpu了

对于交互较强的应用而言,这种垃圾收集齐是不能接受的.一般在java web应用程序中是不会采用串行垃圾回收期的

ParNew回收器(并行回收)

如果说Serial GC是年轻代中的单线程垃圾收集齐,那么ParNew收集齐器则是Serial收集齐的多线程版本

Par是Parallel的缩写,New的意思是处理的只能是新生代

ParNew收集齐除了采用并行回收的方式执行内存回收外,俩款垃圾收集器之间几乎没有任何区别ParNew 收集器在年轻代中同样也是采用复制算法STW机制

PArNew,是很多JVM运行在SErver模式下新生代的默认垃圾收集器

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

对于年代回收次数少,可以使用串行方式节约资源(CPU并行需要切换线程,串行可以省去切换线程的资源)


由于ParNew收集器是基于并行回收,那么是否可以断定ParNew收集齐的效率在任何场景下都会比Serial收集器更高?

  • ParNew收集器运行在多CPU环境下,由于可以充分利用多CPU,多核心的等物理硬件资源优势,可以更快速地完成垃圾收集,提升程序的吞吐量
  • 但是在单个cpu的环境下,ParNew收集器不必Serial收集器更高效.虽然Serial收集器是给予串行回收,但是由于cpu不需要频繁的做任务切换,因此可以有效避免多线程交互过程中产生的一些额外开销

因为除Serial外,目前只有ParNew GC 能和CMS收集器配合工作

在程序中,开发人员可以通过选项"-XX:+UsePArNewGC"手动指定使用ParNew收集器执行内存回收任务.它表示年轻代使用并行收集器,不影响老年代

-XX:ParallelGCThreads限制线程数量,默认开启和cpu数据相同的线程数

Parallel Scavenge(吞吐量优先)

Parallel Scavenge收集器同样采用了复制算法和并行回收和stw机制

那么Parallel收集器的出现是否多此一举?

  • 和ParNew收集器不同,Parallel Scavenge收集器的目标则是大豆一个可控制的吞吐量,它也被称为吞吐量优先的垃圾收集器.
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要的区别

高吞吐量可以高效的地利用cpu时间,晶块完成程序的运算任务,主要适合后台运算而不需要太多交互的任务.因此,常见在服务器环境中使用.例如,那些执行批量处理,订单处理,公司支付,科学计算的应用程序.

Parallel收集器在jdk6时提供了用于执行老年代垃圾收集齐的Parallel Old收集器,用来代替老年代的Serial Old收集器

Parallel Old收集齐采用了标记压缩算法,但同样也是基于并行回收和stw机制

吞吐量有限的情况下,Parallel收集齐和Parallel Old收集器的组合,在Server模式下的内存回收性能和不错

Parallel Scavenge和PArallelOld是jdk1.8中的默认组合

cms(Concurrent-Mark-Sweep)(低延迟,并发)

在1.5时期Hotspot推出了一款在强交互应用中几乎人为有划时代意义的垃圾收集齐CMS收集齐,这款收集齐是HotSPot虚拟机中第一款真正意义上的并发收集齐器,它第一次实现了让垃圾收集线程与用户线程同时工作.

cms收集齐的关注点是尽可能缩短垃圾收集用户停顿的时间.停顿时间越短(低延迟)就越适合用户交互的程序,良好的响应速度能提升用户体验.

目前很大一部分的java应用集中在互联网站或者b/s系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,已给用户带来较好的体验.CMS收集齐器就非常符合这类应用的需求

cms的垃圾收集器算法采用标记清除算法,并且也会stw

CMS作为老年代的收集齐,却无法与JDK1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作,所以在JDK1.5中使用CMS来收集老年代的时候,新生代只能选择PArNew或者Serial中的一个

在G1出现之前,CMS使用还是非常广泛的.直到今天,任然有很多系统使用CMS

CMS整个收集齐的过程比之前的收集齐要更复杂,整个过程分为4个主要阶段,即初始标记阶段,并发标记阶段,重新标记阶段,和并发清除阶段

  • 初始标记:在这个阶段中,程序中所有的工作线程都会因为stw机制从而出现短暂的暂停,这个阶段的主要任务仅仅是标记处GC Roots能直接关联到的对象.一旦标记完成之后就会恢复之前被暂停的所有应用线程.由于直接关联对象比较小,所以这里速度非常快
  • 并发标记阶段:W从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时很长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
  • 重新标记:由于在并发标记过程中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因次为了修正并发标记期间,因用户线程继续运作而导致标记产生变动的那一部分对象标记记录,这个阶段的停顿时间通常回避1初始比较尚未长一些,单远比并发标记时间短.
  • 并发清除:次阶段清理删除掉标记阶段判断依据死亡的对象,释放内存空间.由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

尽管CMS收集齐采用的是并发回收(非独占式),但是其初始化标记和再标记这俩个阶段仍会进行stw机制暂停程序中的工作线程,不过暂停时间不糊太长,因此可以说目前所有的垃圾收集齐都做不到完全不需要stw,只是尽可能的缩短暂停时间.

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

另外由于在垃圾收集阶段用户线程没有中断,所以在CMS回收中,还应该确保用应用程序用户吸纳陈有足够的内存空间可用.因此,cms收集齐并不能像其他收集齐那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用达到某一阈值时,要是cms运行期间预留的内存无法满足程序的需要,就会出现一次Concurrent Mode Failure 失败,这时候虚拟机讲启动后背元:临时启用Serial Old收集齐来重新进行老年代的垃圾回,这样停顿时间就很长了.

CMS收集齐算法采用的是标记清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象占用的内存空间机油可能是不连续的一些内存块,不可避免地会产生一些内存碎片.那么CMS在为新对象分配内存空间时,将无法使用指碰撞技术,而只能够选择空闲列表执行内存分配.

既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?

答案其实很简单,因为当并发清除的时候,用compact整理内存的话,原来的用户线程用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响.Mark Compact更适合STW这种场景下使用.

优点

并发收集

低延迟

cms的弊端

会产生内存碎片,导致并发清除后,用户线程可用的内存空间不足.在无法分配大对象的情况下,不得不提前触发Full GC.

CMS收集齐堆cpu次元非常敏感.在并发阶段,它虽然不会导致用户停顿,但是因为占用了一部分线程而导致应用程序变慢,总吞吐量降低

CMS收集齐无法处理浮动垃圾.可能出现Concurrent Mode Failure失败而导致另一次FullGC产生.在并发标记阶段由于程序的工作线程和垃圾收集线程是同时运行或者交互的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被即时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间

重新标记是修正并发标记阶段怀疑是垃圾的对象这里做确认

jdk9废弃了CMS,14删除了CMS

G1垃圾回收(区域化分代式)

概述

为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间,同时兼顾良好的吞吐量,进一步降低暂停时间,同时兼顾良好的吞吐量

官方给G1设定的目标是在延迟可控的情况下,获得尽可能高的吞吐量,所以才担当起“全功能收集器”的中单与期望

为什么名字叫做Garbage First(G1)呢

  • 因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的)。使用不同的Region来表示Eden,s1,s2,老年代的等
  • G1 GC有计划地避免整个java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许收集的时间,有限回收价值最大的Region
  • 这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字垃圾优先(Garbage First)

G1是一款面向服务器段应用的垃圾收集器,主要针对配备多喝CPU及大容量内存的集器,以及高概率满足GC停顿时间的同时,还兼具高吞吐量的性能特性。

在jdk1.7版本正式启用,移除了Experimental标识,是jdk9以后的默认垃圾回收期,取代了CMS回收期以及Parallel+Parallel Old组合。被Oracle官方常委,全功能的垃圾收集器

在JDK8中还不是默认的垃圾回收器,需要使用-XX:UseG1GC来启用

优缺点

优点
兼具并行并发

并行性:G1在回收期间,可以有多个Gc线程同时工作,有效利用多核集算能力,此时用户线程STW

并发性:G1有用与应用程序交替执行的能力,部分工作可以和应用程序的同时执行,因此一般来说,不会在整个回收阶段发生完全阻塞应用程序的情况。

分代收集

从分代上看,G1依然属于分代型垃圾回收器,它会分区年轻代和老年代,年轻代依然有Eden区和Survivor区。但堆的结构上看,它不要求整个Eden区,年轻代或者老年代都是连续的,在也不坚持固定大小和固定数量。

将堆空间分为若干个区域(Region)这些区域中包含了逻辑上的年轻代和老年代。

和之前的各类回收器不同,它还同时兼顾年轻代和老年代。对比其他回收期,或者工作在年轻代,或者工作在老年代

空间整合

CMS”标记清楚算法“会产生内存碎片,要在若干次GC之后进行一次碎片整理

G1将内存划分为一个个的Region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记压缩算法,俩种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC。尤其当Java堆非常大的时候,G1的优势更加明显

可预测停顿时间模型

这是G1相对于CMS的另一大优势,G1除了追求低停顿之外,海恩那个建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间内,小号在垃圾收集器的时间上不得超过N毫秒

  • 由于分区的原因,G1可以只选取部分的区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制
  • TGG1,跟踪各个Region里面的堆积价值大小(回收所获得的空间大小以及回收所需的经验值),在后台维护一个优先列表,每次根据允许的手机时间,优先回收价值最大的Region。保证了G1收集器在有限时间内可以获取尽可能高的收集概率。
  • 相比于CMS GC ,G1未必能做到CMS在最好的情况下的延时停顿,但是最差情况要好很多
缺点

相较于CMS,G1还不具备全方位,压倒性的优势。比如在用户程序运行过程中,G1无论是为了垃圾收集产生的内存占用还是程序运行的额外负载都比CMS要高

从经验上来说,在小内存应用上CMS的表现大概率会优于G1,二G1在大内存应用上则发挥其优势。平衡点在6-8G之间

使用场景

面向服务端应用,针对具有大内存,多处理器的集器(在普通大小的堆里表现并不惊喜)

主要的应用是GC低延迟,并具有大堆的应用程序提供解决方案

如:在堆大小约是6GB或者更大时,可预测的暂停时间可以低于0.5秒(G1通过每次清理一部分而不是全部Region的增量方式来清理保证每次GC停顿时间不会过长)

替换1.5的CMS收集器

在下面情的情况时,使用G1可能比CMS收集器好

  • 超过50%的java堆被活动数据占用
  • 对象分配频率或年代替身频率变化很大
  • GC停顿时间过长

hotspot垃圾收集器里,除了G1意外,其他的垃圾收集器使用内置的JVM线程执行的多线程操作,二G1 可以采用应用线程承担后台运行的GC工作,即当1JVM的GC线程处理速度慢时,系统会调用应用程序线程帮助加速垃圾回收过程。

分区Region

使用G1收集器时,它将整个java堆划分为2048个大小相同独立的Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1mb到32mb之间,切为2

的N次幂,.可以通过XX:G1HeapRegionSize设定,所有的Region大小相同,切在JVm生命周期内不会被改变

虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region(不需要连续)集合.通过Region的动态分配方式实现逻辑上的连续


一个region有可能属于Eden,survivor或者Old/Tenured内存区域.但是一个region指可能属于一个角色.途中的E表示该region属于Eden内存区域,S表示属于Survivor内存区域,)表示Old内存区域,空白表示没使用的内存空间

G1垃圾收集齐还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块.主要用于存储大对象,如果超过1.5个region,就放到H

设置H的原因

对于堆中的大对象,默认直接会被分配到老年代.但是如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响.为了解决这个问题,G1划分了一个Humongous区,它专门用来存放大对象.如果H区装不下大对象,那么G1会须按照连续的H区来存储.为了能找到连续的H区,有时候不得不启动Full GC.G1的大多数行为都把H区最为老年代的一部分来看待


G1 GC的垃圾回收过程主要包括如下三个环节

  • 年轻代GC
  • 老年代并发标记过程
  • 混合回收

(如果需要,线程独占式,高强度的Full GC还是存在的.它针对GC 评估提供了一种失败保护机制,即强力回收)

记忆集

一个Region不可能是孤立的,一个Region中的对象可能被其他任意Region中对象引用,判断对象存活时,是否需要扫描整个java堆,回收新生代也不得不同时扫描老年代.这样会降低GC的效率

解决方法:

无论G1还是其他分代收集齐,JVM都是使用Remembered Set来避免全局扫描:

  • 每个Region都有一个Remembered Set(记忆集)
  • 每次Reference(引用)类型操作数据写操作时,都会产生一个Write Barrier(写屏障)暂时中断操作
  • 然后检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region(其他收集器,检查老年代是否引用了新生代对象)
  • 如果不同,通过CardTable(卡表)吧相关引用信息记录到引用指向对象的所在的Region对于的Remembered Set中
  • 当进行垃圾回收时,在GC根节点的灭局范围假加入Remembered Set就可以保证不进行全局扫描,也不会有遗漏

回收过程


应用程序分配内存,当年轻代的Eden区用尽时开始年轻代回收过程,G1的年轻代收集齐阶段是一个并行的独占式的收集齐.在年轻代回收期,G1暂停所有应用程序线程,启动多线程执行年轻代回收.然后从年轻代区间移动存活对象到survivor区间或者老年代区间,也有可能是俩个区间都会涉及

当堆内存使用达到45%时,开始老年代并发标记过程

标记完成马上开始混合回收过程.对于一个混合回收期,G1 GC从老年区间移动存活对象到空闲区间,这些空闲区间也就成为了老年代的一部分.和年轻代不同,老年代的G1回收期和其他GC不同,G1的老年代回收期不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的Region就可以了.同时,这个老年代的Region是和年轻代一起被回收的

年轻代gc

jvm启动时,G1先准备好Eden区,程序运行过程中不断创建对象到Eden区,当Eden区空间耗尽时,G1将会启动一次年轻代的垃圾收集过程.

年轻代回收只会回收Eden区和Survivor区

YGC时首先停止应用程序的执行stw,G1创建垃圾回收集(Collection Set),回收集是指需要被回收的内存分段的集合,年轻代回收过程包含年轻代Eden区和Survivor区所有的内存分段

第一阶段扫描根

跟是指 static变量指向的对象,正在执行的方法我调用链上的局部变量等.根引用同RSet(记忆集)记录的外部引用作为扫描存活对象入口

第二阶段更新RSet

处理dirt card queue(脏卡表)中的card,更新RSet.此阶段完成后,RSet可以准确地反映老年代所在的内存分段对象中的引用

对于应用程序的复制语句,jvm会在之前和之后执行特殊的操作以在diry card queue中入队保存了对象引用信息的card.在年轻代回收的时候,G1会对Dirt CArdQueue中所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系

为什么不在赋值语句处直接更新Rest呢?这是为了性能的需要,RSet的处理需要线程同步,开销会很大,使用队列性能会好很多

第三处理RSet

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

第四阶段复制对象

次阶段,对象树被遍历,Eden区内存短中存活的对象会被复制到survivor区中空的内存分段,survivor区内存中断存活的对象如果年龄未达阈值,年龄+1,达到阈值将会被复制到老年代中空内存分段,如果Survivor空间不够,Eden空间的部分数据将会直接晋升到老年代空间

第五阶段处理引用

处理强软弱虚等引用.最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可达到内存整理的效果,减少碎片.

并发标记

初始标记阶段:标记从根节点直接可达的对象.这个阶段是STW的,并且会触发一次年轻代GC

跟区域扫描:G1 GC扫描survivor区可直接可达的老年代区域对象,并标记被引用的对象.这个过程必须在young gc之前完成

并发标记:在整个堆中进行并发标记(和应用程序并发执行),此过程可能会被Yong GC中断.在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收.同时并发表及过程中,会计算每个区域的对象活性(去榆中存活对象的比例)

再次标记:由于应用程序持续进行,需要修正上一次的标记结果.是STW的.G1采用了比CMS更快的算法那SATB

独占清理:计算机各个区域的存活对象和GC回收比例,并进行排序,识别可疑混合回收的区域.为下阶段做铺垫.是STW的(主要针对的是老年代)

并发清理阶段:识别并清理完全空闲的区域

混合回收

当越来越多的对象晋升到Old Region时,为了避免内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC,该算法不是Old GC,除了回收整个YoungRegion,还会回收一部分Old Region.这里需要注意的是:一部分老年代而不是全部老年代.可以选择哪些Old Region进行收集,从而可以对垃圾回收的耗时进行控制.也要注意哦的是Mixed GC 并不是Full GC

G1的初衷就是要避免Fll GC的出现.但是如果上述方式不能正常工作,G1会停止引用程序的执行,使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用停顿时间很长

当内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到Full gc,这种情况可用通过增大内存解决

导致Full GC的原因有俩个:

Evacuation的时候没有足够的to-space来存放晋升的对象

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

总结

垃圾收集器分类作用位置使用算法特点适用场景
Serial串行运行新生代复制算法响应速度优先适用于但CPU环境下的client模式
ParNew并行运行新生代复制算法响应速度优先多CPU环境Server模式下与CMS配合使用
Parallel并行运行新生代复制算法吞吐量优先适用于后台运算而不需要太多交互的场景
Serial Old串行运行老年代标记压缩算法响应速度优先适用于但CPU环境下的client模式
Parallel Old并发运行老年代标记压缩算法吞吐量优先适用于后台运算而不需要太多交互的场景
CMS并发运行老年代标记清除算法响应速度优先适用于互联网或B/S业务
G1并发,并行运行老年代,新生代标记压缩算法,复制算法响应速度优先面向服务器端应用java垃圾收集器

怎么选择垃圾收集器?

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

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

如果是单核,单机程序,并且没有停顿时间的要求,串行收集器

如果是多cpu,追求地停顿时间,需快速响应,使用并发收集齐

官方推荐G1,性能高,如今互联网的项目,基本都是使用G1

其他垃圾回收器

ZGC

ZGC收集器是一款给予Region内存布局的,(暂时)不设分带的,使用了读屏障,热色值真和内存多重映射等技术实现可并发的标记-压缩算法的,以低延迟为首要目标的一款垃圾收集器

ZGC的工作过程可分为4个阶段:并发标记-并发预备重分配-并发重分配-并发重映射等

ZGC几乎在所有地方并发执行的,除了初始标记是STW的.所以停顿时间几乎就耗费在初始标记上,这部分的停顿时间是非常少的.

在jdk14之前,ZGC仅linux才支持

ALiGC

aliGC是阿里巴巴JVM团队基于G1算法,面向大堆应用场景.

Last modification:February 1, 2023
如果觉得我的文章对你有用,请随意赞赏