一、GC分类与性能指标

1.1 垃圾收集器分类

(1)按线程数分

按线程数分(垃圾回收线程数),能够分为串行垃圾回收器和并行垃圾回收器。

串行回收指的是在同一时间段内只容许有一个CPU用于执行垃圾回收操作,此时工作线程被暂停,直至垃圾收集工作完结。

  • 在诸如单CPU处理器或者较小的利用内存等硬件平台不是特地优越的场合,串行回收器的性能体现能够超过并行回收器和并发回收器。所以,串行回收默认被利用在客户端的Client模式下的JVM中
  • 在并发能力比拟强的CPU上,并行回收器产生的进展工夫要短于串行回收器。

和串行回收相同,并行收集能够使用多个CPU同时执行垃圾回收,因而晋升了利用的吞吐量,不过并行回收依然与串行回收一样,采纳独占式,应用了“stop-the-world”机制。

(2)按工作模式分

依照工作模式分,能够分为并发式垃圾回收器和独占式垃圾回收器。

  • 并发式垃圾回收器与应用程序线程交替工作,以尽可能减少应用程序的进展工夫。
  • 独占式垃圾回收器(Stop the world)一旦运行,就进行应用程序中的所有用户线程,直到垃圾回收过程齐全完结。

(3)其余分类

按碎片解决形式分,可分为压缩武垃圾回收器和非压缩式垃圾回收器。

  • 压缩式垃圾回收器会在回收实现后,对存活对象进行压缩整顿,打消回收后的碎片。

    • 再调配对象空间应用:指针碰撞
  • 非压缩式的垃圾回收器不进行这步操作。

    • 再调配对象空间应用:闲暇列表

还能够按工作的内存区间分,又分为年老代垃圾回收器和老年代垃圾回收器。

1.2 性能指标

  • 吞吐量:运行用户代码的工夫占总运行工夫的比例

    • (总运行工夫 = 程序的运行工夫 + 内存回收的工夫)
  • 垃圾收集开销:吞吐量的补数,垃圾收集所用工夫与总运行工夫的比例。
  • 暂停工夫:执行垃圾收集时,程序的工作线程被暂停的工夫。
  • 收集频率:绝对于应用程序的执行,收集操作产生的频率。
  • 内存占用:Java堆区所占的内存大小。
  • 疾速:一个对象从诞生到被回收所经验的工夫。

吞吐量、暂停工夫、内存占用 这三者独特形成一个“不可能三角”。三者总体的体现会随着技术提高而越来越好。

这三项里,暂停工夫的重要性日益凸显。因为随着硬件倒退,内存占用多些越来越能容忍,硬件性能的晋升也有助于升高收集器运行时对应用程序的影响,即进步了吞吐量。而内存的扩充,对提早反而带来负面成果。
简略来说,次要抓住两点:

  • 吞吐量
  • 暂停工夫

(1)性能指标:吞吐量

吞吐量就是CPU用于运行用户代码的工夫与CPU总耗费工夫的比值,即吞吐量=运行用户代码工夫 /(运行用户代码工夫+垃圾收集工夫)

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

这种状况下,应用程序能容忍较高的暂停工夫,因而,高吞吐量的应用程序有更长的工夫基准,疾速响应是不用思考的。

吞吐量优先,意味着在单位工夫内,STW的工夫最短:0.2+0.2=0.4

(2)性能指标:暂停工夫

“暂停工夫”是指一个时间段内应用程序线程暂停,让GC线程执行的状态。

例如,GC期间1ee毫秒的暂停工夫意味着在这100毫秒期间内没有应用程序线程是流动的。暂停工夫优先,意味着尽可能让单次STW的工夫最短:0.1+0.1 + 0.1+ 0.1+ 0.1=0.5s

(3)吞吐量vs暂停工夫

高吞吐量较好因为这会让应用程序的最终用户感觉只有应用程序线程在做“生产性”工作。直觉上,吞吐量越高程序运行越快。

低暂停工夫(低提早)较好因为从最终用户的角度来看不论是GC还是其余起因导致一个利用被挂起始终是不好的。这取决于应用程序的类型,有时候甚至短暂的200毫秒暂停都可能打断终端用户体验。因而,具备低的较大暂停工夫是十分重要的,特地是对于一个交互式应用程序。

可怜的是”高吞吐量”和”低暂停工夫”是一对相互竞争的指标(矛盾)。

因为如果抉择以吞吐量优先,那么必然须要升高内存回收的执行频率,然而这样会导致GC须要更长的暂停工夫来执行内存回收。

相同的,如果抉择以低提早优先为准则,那么为了升高每次执行内存回收时的暂停工夫,也只能频繁地执行内存回收,但这又引起了年老代内存的缩减和导致程序吞吐量的降落。

在设计(或应用)GC算法时,咱们必须确定咱们的指标:一个GC算法只可能针对两个指标之一(即只专一于较大吞吐量或最小暂停工夫),或尝试找到一个二者的折中。

当初规范:在最大吞吐量优先的状况下,升高暂停工夫

二、不同的垃圾回收器概述

垃圾收集机制是Java的招牌能力,极大地提高了开发效率。这当然也是面试的热点。

那么,Java常见的垃圾收集器有哪些?

GC垃圾收集器是和JVM一脉相承的,它是和JVM进行搭配应用,在不同的应用场景对应的收集器也是有区别

2.1 垃圾回收器发展史

有了虚拟机,就肯定须要收集垃圾的机制,这就是Garbage Collection,对应的产品咱们称为Garbage Collector。

  • 1999年随JDK1.3.1一起来的是串行形式的serialGc,它是第一款GC。ParNew垃圾收集器是Serial收集器的多线程版本。
  • 2002年2月26日,Parallel GC和Concurrent Mark Sweep GC追随JDK1.4.2一起公布。
  • Parallel GC在JDK6之后成为HotSpot默认GC。
  • 2012年,在JDK1.7u4版本中,G1可用。
  • 2017年,JDK9中G1变成默认的垃圾收集器,以代替CMS。
  • 2018年3月,JDK10中G1垃圾回收器的并行残缺垃圾回收,实现并行性来改善最坏状况下的提早。
  • 2018年9月,JDK11公布。引入Epsilon 垃圾回收器,又被称为 "No-Op(无操作)“ 回收器。同时,引入ZGC:可伸缩的低提早垃圾回收器(Experimental)。
  • 2019年3月,JDK12公布。加强G1,主动返回未用堆内存给操作系统。同时,引入Shenandoah GC:低进展工夫的GC(Experimental)。
  • 2019年9月,JDK13公布。加强YGC,主动返回未用堆内存给操作系统。
  • 2020年3月,JDK14公布。删除CMS垃圾回收器。扩大ZGC在macos和Windows上的利用。

2.2 7种经典的垃圾收集器

  • 串行回收器:Serial、Serial old
  • 并行回收器:ParNew、Parallel Scavenge、Parallel old
  • 并发回收器:CMS、G11

7款经典收集器与垃圾分代之间的关系

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

老年代收集器:Serial old、Parallel old、CMS

整堆收集器:G1

2.3 垃圾收集器的组合关系

  • 两个收集器间有连线,表明它们能够搭配应用:Serial/Serial Old、Serial/CMS、ParNew/Serial Old、ParNew/CMS、Parallel Scavenge/Serial Old、Parallel Scavenge/Parallel Old、G1;
  • 其中Serial Old作为CMS呈现"Concurrent Mode Failure"失败的后备预案。
  • (红色虚线)因为保护和兼容性测试的老本,在JDK 8时将Serial+CMS、ParNew+Serial Old这两个组合申明为废除(JEP173),并在JDK9中齐全勾销了这些组合的反对(JEP214),即:移除。
  • (绿色虚线)JDK14中:弃用Parallel Scavenge和Serialold GC组合(JEP366)
  • (青色虚线)JDK14中:删除CMS垃圾回收器(JEP363)

为什么要有很多收集器,一个不够吗?因为Java的应用场景很多,挪动端,服务器等。所以就须要针对不同的场景,提供不同的垃圾收集器,进步垃圾收集的性能。

尽管咱们会对各个收集器进行比拟,但并非为了筛选一个最好的收集器进去。没有一种放之四海皆准、任何场景下都实用的完满收集器存在,更加没有万能的收集器。所以咱们抉择的是对具体利用最合适的收集器

如何查看默认垃圾收集器

-XX:+PrintCommandLineFlags:查看命令行相干参数(蕴含应用的垃圾收集器)

应用命令行指令:jinfo -flag 相干垃圾回收器参数 过程ID

三、Serial回收器:串行回收

Serial收集器是最根本、历史最悠久的垃圾收集器了。JDK1.3之前回收新生代惟一的抉择。

Serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器,采纳复制算法、串行回收和"Stop-The-World"机制的形式执行内存回收

除了年老代之外,Serial收集器还提供用于执行老年代垃圾收集的Serial Old收集器。Serial Old收集器同样也采纳了串行回收和"Stop-The-World"机制,只不过内存回收算法应用的是标记-压缩算法

  • Serial Old是运行在Client模式下默认的老年代的垃圾回收器
  • Serial Old在Server模式下次要有两个用处:

    • 与新生代的Parallel scavenge配合应用
    • 作为老年代CMS收集器的后备垃圾收集计划

这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅阐明它只会应用一个CPU或一条收集线程去实现垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其余所有的工作线程,直到它收集完结(Stop The World)

劣势:简略而高效(与其余收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器因为没有线程交互的开销,分心做垃圾收集天然能够取得最高的单线程收集效率。运行在Client模式下的虚拟机是个不错的抉择。

在用户的桌面利用场景中,可用内存个别不大(几十MB至一两百MB),能够在较短时间内实现垃圾收集(几十ms至一百多ms),只有不频繁产生,应用串行回收器是能够承受的。

在HotSpot虚拟机中,应用-XX:+UseSerialGC参数能够指定年老代和老年代都应用串行收集器。

四、ParNew回收器:并行回收

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

  • Par是Parallel的缩写,New:只能解决的是新生代

ParNew 收集器除了采纳并行回收的形式执行内存回收外,两款垃圾收集器之间简直没有任何区别。ParNew收集器在年老代中同样也是采纳复制算法、"Stop-The-World"机制

ParNew 是很多JVM运行在Server模式下新生代的默认垃圾收集器。

  • 对于新生代,回收次数频繁,应用并行形式高效。
  • 对于老年代,回收次数少,应用串行形式节俭资源。(CPU并行须要切换线程,串行能够省去切换线程的资源)
  • 除Serial外,目前只有ParNew GC能与CMS收集器配合工作。

在程序中,开发人员能够通过选项-XX:+UseParNewGC手动指定应用ParNew收集器执行内存回收工作。它示意年老代应用并行收集器,不影响老年代。

-XX:ParallelGCThreads限度线程数量,默认开启和CPU数据雷同的线程数。

五、Parallel回收器:吞吐量优先

HotSpot的年老代中除了领有ParNew收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采纳了复制算法、并行回收和"Stop-The-World"机制

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

  • 和ParNew收集器不同,ParallelScavenge收集器的指标则是达到一个可管制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
  • 自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。

高吞吐量则能够高效率地利用CPU工夫,尽快实现程序的运算工作,次要适宜在后盾运算而不须要太多交互的工作。因而,常见在服务器环境中应用。例如,那些执行批量解决、订单解决、工资领取、科学计算的应用程序

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

Parallel Old收集器采纳了标记-压缩算法,但同样也是基于并行回收和"Stop-The-World"机制。

在程序吞吐量优先的利用场景中,Parallel收集器和Parallel Old收集器的组合,在Server模式下的内存回收性能很不错。在Java8中,默认是此垃圾收集器

参数配置

-XX:+UseParallelGC :手动指定年老代应用Parallel并行收集器执行内存回收工作。

-XX:+UseParallelOldGC :手动指定老年代都是应用并行回收收集器。

  • 别离实用于新生代和老年代。默认jdk8是开启的。
  • 下面两个参数,默认开启一个,另一个也会被开启。(相互激活

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

  • 个别地,最好与CPU数量相等,以防止过多的线程数影响垃圾收集性能。
  • 在默认状况下,当CPU数量小于8个,ParallelGCThreads的值等于CPU数量;当CPU数量大于8个,ParallelGCThreads的值等于3+[5*CPU Count]/8]

XX:MaxGCPauseMillis :设置垃圾收集器最大进展工夫(即STW的工夫),单位是毫秒。(慎用

  • 为了尽可能地把进展工夫管制在MaxGCPauseMi11s以内,收集器在工作时会调整Java堆大小或者其余一些参数。对于用户来讲,进展工夫越短体验越好。然而在服务器端,咱们重视高并发,整体的吞吐量。所以服务器端适宜Parallel,进行管制。

-XX:GCTimeRatio:垃圾收集工夫占总工夫的比例( = 1/(N+1))。用于掂量吞吐量的大小,取值范畴(0,100),默认值99,也就是垃圾回收工夫不超过1。

  • 与前一个-XX:MaxGCPauseMillis参数有肯定矛盾性。暂停工夫越长,Radio参数就容易超过设定的比例。

-XX:+UseAdaptivesizepplicy :设置Parallel scavenge收集器具备自适应调节策略。

  • 在这种模式下,年老代的大小、Eden和Survivor的比例、降职老年代的对象年龄等参数会被主动调整,已达到在堆大小、吞吐量和进展工夫之间的平衡点。
  • 在手动调优比拟艰难的场合,能够间接应用这种自适应的形式,仅指定虚拟机的最大堆、指标的吞吐量(GCTimeRatio)和进展工夫(MaxGCPauseMil1s),让虚拟机本人实现调优工作。

六、CMS回收器:低提早

6.1 概述

在JDK1.5期间,Hotspot推出了一款在强交互利用中简直可认为有划时代意义的垃圾收集器:cMS(Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作

CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的进展工夫。进展工夫越短(低提早)就越适宜与用户交互的程序,良好的响应速度能晋升用户体验。

目前很大一部分的Java利用集中在互联网站或者B/S零碎的服务端上,这类利用尤其器重服务的响应速度,心愿零碎进展工夫最短,以给用户带来较好的体验。CMS收集器就十分合乎这类利用的需要。

CMS的垃圾收集算法采纳标记-革除算法,并且也会"Stop-The-World"。

可怜的是,CMS作为老年代的收集器,却无奈与JDK1.4.0中曾经存在的新生代收集器Parallel Scavenge配合工作,所以在JDK1.5中应用CMS来收集老年代的时候,新生代只能抉择ParNew或者Serial收集器中的一个。

在G1呈现之前,CMS应用还是十分宽泛的。始终到明天,依然有很多零碎应用CMS GC。

CMS整个过程比之前的收集器要简单,整个过程分为4个次要阶段,即初始标记阶段、并发标记阶段、从新标记阶段和并发革除阶段。(波及STW的阶段次要是:初始标记 和 从新标记)

  • 初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为“stop-the-world”机制而呈现短暂的暂停,这个阶段的次要工作仅仅只是标记出GC Roots能间接关联到的对象。一旦标记实现之后就会复原之前被暂停的所有利用线程。因为间接关联对象比拟小,所以这里的速度十分快。
  • 并发标记(Concurrent-Mark)阶段:从GC Roots的间接关联对象开始遍历整个对象图的过程,这个过程耗时较长然而不须要进展用户线程,能够与垃圾收集线程一起并发运行。
  • 从新标记(Remark)阶段:因为在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者穿插运行,因而为了修改并发标记期间,因用户程序持续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的进展工夫通常会比初始标记阶段稍长一些,但也远比并发标记阶段的工夫短。
  • 并发革除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的曾经死亡的对象,开释内存空间。因为不须要挪动存活对象,所以这个阶段也是能够与用户线程同时并发的

6.2 优缺点剖析

只管CMS收集器采纳的是并发回收(非独占式),然而在其初始化标记和再次标记这两个阶段中依然须要执行“Stop-The-World”机制暂停程序中的工作线程,不过暂停工夫并不会太长,因而能够阐明目前所有的垃圾收集器都做不到齐全不须要“Stop-The-World”,只是尽可能地缩短暂停工夫。

因为最消耗工夫的并发标记与并发革除阶段都不须要暂停工作,所以整体的回收是低进展的。

另外,因为在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因而,CMS收集器不能像其余收集器那样等到老年代简直齐全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中仍然有足够的空间反对利用程序运行。要是CMS运行期间预留的内存无奈满足程序须要,就会呈现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:长期启用Serial old收集器来从新进行老年代的垃圾收集,这样进展工夫就很长了。

CMS收集器的垃圾收集算法采纳的是标记革除算法,这意味着每次执行完内存回收后,因为被执行内存回收的无用对象所占用的内存空间极有可能是不间断的一些内存块,不可避免地将会产生一些内存碎片。那么CMS在为新对象分配内存空间时,将无奈应用指针碰撞(Bump the Pointer)技术,而只可能抉择闲暇列表(Free List)执行内存调配。

CMS为什么不应用标记整顿算法?

答案其实很简答,因为当并发革除的时候,用Compact整顿内存的话,原来的用户线程应用的内存还怎么用呢?要保障用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适宜“stop the world”这种场景下应用。

长处

  • 并发收集
  • 低提早

毛病

  • 会产生内存碎片,导致并发革除后,用户线程可用的空间有余。在无奈调配大对象的状况下,不得不提前触发FullGC。
  • CMS收集器对CPU资源十分敏感。在并发阶段,它尽管不会导致用户进展,然而会因为占用了一部分线程而导致应用程序变慢,总吞吐量会升高。
  • CMS收集器无奈解决浮动垃圾。可能呈现“Concurrent Mode Failure"失败而导致另一次Full GC的产生。在并发标记阶段因为程序的工作线程和垃圾收集线程是同时运行或者穿插运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS将无奈对这些垃圾对象进行标记,最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时开释这些之前未被回收的内存空间。

6.3 设置参数

  • -XX:+UseConcMarkSweepGC手动指定应用CMS收集器执行内存回收工作。

开启该参数后会主动将-XX:+UseParNewGC关上。即:ParNew(Young区用)+CMS(Old区用)+Serial Old的组合。

  • -XX:CMSInitiatingOccupanyFraction 设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。

JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收。JDK6及以上版本默认值为92%

如果内存增长迟缓,则能够设置一个稍大的值,大的阀值能够无效升高CMS的触发频率,缩小老年代回收的次数能够较为显著地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该升高这个阈值,以防止频繁触发老年代串行收集器。因而通过该选项便能够无效升高Full GC的执行次数

  • -XX:+UseCMSCompactAtFullCollection用于指定在执行完Full GC。

GC后对内存空间进行压缩整顿,以此防止内存碎片的产生。不过因为内存压缩整顿过程无奈并发执行,所带来的问题就是进展工夫变得更长了。

  • -XX:CMSFullGCsBeforecompaction 设置在执行多少次Full GC后对内存空间进行压缩整顿。
  • -XX:ParallelcMSThreads 设置CMS的线程数量。

CMS默认启动的线程数是(ParallelGCThreads+3)/4,ParallelGCThreads是年老代并行收集器的线程数。当CPU资源比拟缓和时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会十分蹩脚。

小结

HotSpot有这么多的垃圾回收器,那么如果有人问,Serial GC、Parallel GC、Concurrent Mark Sweep GC这三个GC有什么不同呢?

  • 如果你想要最小化地应用内存和并行开销,请选Serial GC;
  • 如果你想要最大化应用程序的吞吐量,请选Parallel GC;
  • 如果你想要最小化GC的中断或进展工夫,请选CMs GC。

JDK后续版本中CMS的变动

JDK9新个性:CMS被标记为Deprecate了(JEP291)>如果对JDK9及以上版本的HotSpot虚拟机应用参数

-XX:+UseConcMarkSweepGC来开启CMS收集器的话,用户会收到一个正告信息,提醒CMS将来将会被废除。

JDK14新个性:删除CMS垃圾回收器(JEP363)移除了CMS垃圾收集器,如果在JDK14中应用
XX:+UseConcMarkSweepGC的话,JVM不会报错,只是给出一个warning信息,然而不会exit。JVM会主动回退以默认GC形式启动JVM。

七、G1回收器:区域化分代式

7.1 概述

既然咱们曾经有了后面几个弱小的GC,为什么还要公布Garbage First(G1)?

起因就在于应用程序所应答的业务越来越宏大、简单,用户越来越多,没有GC就不能保障应用程序失常进行,而常常造成STW的GC又跟不上理论的需要,所以才会一直地尝试对GC进行优化。G1(Garbage-First)垃圾回收器是在Java7 update4之后引入的一个新的垃圾回收器,是当今收集器技术倒退的最前沿成绩之一。

与此同时,为了适应当初不断扩大的内存和一直减少的处理器数量,进一步升高暂停工夫(pause time),同时兼顾良好的吞吐量。

官网给G1设定的指标是在提早可控的状况下取得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与冀望

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

因为G1是一个并行回收器,它把堆内存宰割为很多不相干的区域(Region)(物理上不间断的)。应用不同的Region来示意Eden、幸存者0区,幸存者1区,老年代等。

G1 GC有打算地防止在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region外面的垃圾沉积的价值大小(回收所取得的空间大小以及回收所需工夫的经验值),在后盾保护一个优先列表,每次依据容许的收集工夫,优先回收价值最大的Region

因为这种形式的侧重点在于回收垃圾最大量的区间(Region),所以咱们给G1一个名字:垃圾优先(Garbage First)。

G1(Garbage-First)是一款面向服务端利用的垃圾收集器,次要针对装备多核CPU及大容量内存的机器,以极高概率满足GC进展工夫的同时,还兼具高吞吐量的性能特色。

在JDK1.7版本正式启用,移除了Experimenta1的标识,是JDK9当前的默认垃圾回收器,取代了CMS回收器以及Parallel+Parallel Old组合。被orac1e官网称为“全功能的垃圾收集器”。

与此同时,CMS曾经在JDK9中被标记为废除(Deprecated)。在jdk8中还不是默认的垃圾回收器,须要应用-XX:+UseG1GC来启用。

7.2 优缺点

(1)G1垃圾收集器的长处

与其余GC收集器相比,G1应用了全新的分区算法,其特点如下所示:

并行与并发

  • 并行性:G1在回收期间,能够有多个GC线程同时工作,无效利用多核计算能力。此时用户线程STW。
  • 并发性:G1领有与应用程序交替执行的能力,局部工作能够和应用程序同时执行。因而,一般来说,不会在整个回收阶段产生齐全阻塞应用程序的状况。

分代收集

  • 从分代上看,G1仍然属于分代型垃圾回收器,它会辨别年老代和老年代,年老代仍然有Eden区和Survivor区。但从堆的构造上看,它不要求整个Eden区、年老代或者老年代都是间断的,也不再保持固定大小和固定数量。
  • 将堆空间分为若干个区域(Region),这些区域中蕴含了逻辑上的年老代和老年代。
  • 和之前的各类回收器不同,它同时兼顾年老代和老年代。比照其余回收器,或者工作在年老代,或者工作在老年代;

G1所谓的分代,曾经不是上面这样的:

而是这样的一个区域:

空间整合

  • CMS:“标记-革除”算法、内存碎片、若干次GC后进行一次碎片整顿。
  • G1将内存划分为一个个的region。内存的回收是以region作为根本单位的。Region之间是复制算法,但整体上理论可看作是标记-压缩(Mark-Compact)算法,两种算法都能够防止内存碎片。这种个性有利于程序长时间运行,调配大对象时不会因为无奈找到间断内存空间而提前触发下一次GC。尤其是当Java堆十分大的时候,G1的劣势更加显著。

可预测的进展工夫模型(即:软实时soft real-time)
这是G1绝对于CMS的另一大劣势,G1除了谋求低进展外,还能建设可预测的进展工夫模型,能让使用者明确指定在一个长度为M毫秒的工夫片段内,耗费在垃圾收集上的工夫不得超过N毫秒。

  • 因为分区的起因,G1能够只选取局部区域进行内存回收,这样放大了回收的范畴,因而对于全局进展状况的产生也能失去较好的管制。
  • G1跟踪各个Region外面的垃圾沉积的价值大小(回收所取得的空间大小以及回收所需工夫的经验值),在后盾保护一个优先列表,每次依据容许的收集工夫,优先回收价值最大的Region。保障了G1收集器在无限的工夫内能够获取尽可能高的收集效率。
  • 相比于CMSGC,G1未必能做到CMS在最好状况下的延时进展,然而最差状况要好很多。

(2)G1垃圾收集器的毛病

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

从教训上来说,在小内存利用上CMS的体现大概率会优于G1,而G1在大内存利用上则施展其劣势。平衡点在6-8GB之间。

7.3 G1参数设置

  • -XX:+UseG1GC:手动指定应用G1垃圾收集器执行内存回收工作
  • -XX:G1HeapRegionSize:设置每个Region的大小。值是2的幂,范畴是1MB到32MB之间,指标是依据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
  • -XX:MaxGCPauseMillis :设置冀望达到的最大GC进展工夫指标(JVM会尽力实现,但不保障达到)。默认值是200ms。
  • -XX:+ParallelGcThread: 设置STW工作线程数的值。最多设置为8。
  • -XX:ConcGCThreads :设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGcThreads)的1/4左右。
  • -XX:InitiatingHeapOccupancyPercent :设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

G1收集器的常见操作步骤

G1的设计准则就是简化JVM性能调优,开发人员只须要简略的三步即可实现调优:

  • 第一步:开启G1垃圾收集器
  • 第二步:设置堆的最大内存
  • 第三步:设置最大的进展工夫

G1中提供了三种垃圾回收模式:YoungGC、Mixed GC和Fu11GC,在不同的条件下被触发。

7.4 G1收集器的实用场景

面向服务端利用,针对具备大内存、多处理器的机器。(在一般大小的堆里体现并不惊喜)

最次要的利用是须要低GC提早,并具备大堆的应用程序提供解决方案;

如:在堆大小约6GB或更大时,可预测的暂停工夫能够低于e.5秒;(G1通过每次只清理一部分而不是全副的Region的增量式清理来保障每次Gc进展工夫不会过长)。
用来替换掉JDK1.5中的CMS收集器;在上面的状况时,应用61可能比CMS好:

  • 超过5e%的Java堆被流动数据占用;
  • 对象调配频率或年代晋升频率变化很大;
  • GC进展工夫过长(长于e.5至1秒)

HotSpot垃圾收集器里,除了61以外,其余的垃圾收集器应用内置的JVM线程执行Gc的多线程操作,而G1GC能够采纳利用线程承当后盾运行的GC工作,即当JVM的GC线程处理速度慢时,零碎会调用应用程序线程帮忙减速垃圾回收过程。

7.5 分区Region:化整为零

应用G1收集器时,它将整个Java堆划分成约2048个大小雷同的独立Region块,每个Region块大小依据堆空间的理论大小而定,整体被管制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。能够通过

XX:G1HeapRegionsize设定。所有的Region大小雷同,且在JVM生命周期内不会被扭转

尽管还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不须要间断)的汇合。通过Region的动态分配形式实现逻辑上的间断。

一个region有可能属于Eden,Survivor或者old/Tenured内存区域。然而一个region只可能属于一个角色。图中的E示意该region属于Eden内存区域,s示意属于survivor内存区域,o示意属于01d内存区域。图中空白的示意未应用的内存空间。

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

设置H的起因:对于堆中的对象,默认间接会被调配到老年代,然而如果它是一个短期存在的大对象就会对垃圾收集器造成负面影响。为了解决这个问题,G1划分了一个Humongous区,它用来专门寄存大对象。如果一个H区装不下一个大对象,那么G1会寻找间断的H区来存储。为了能找到间断的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来对待。

每个Region都是通过指针碰撞来调配空间

7.6 Remembered Set(记忆集)

一个Region不可能是孤立的,一个Region中的对象可能被其余任意Region中对象援用,判断对象存活时,是否须要扫描整个Java堆能力保障精确?

在其余的分代收集器,也存在这样的问题(而G1更突出)回收新生代也不得不同时扫描老年代?这样会升高MinorGC的效率。

解决办法:

无论G1还是其余分代收集器,JVM都是应用Remembered Set来防止全局扫描;

每个Region都有一个对应的Remembered Set

每次Reference类型数据写操作时,都会产生一个Write Barrier临时中断操作;

而后查看将要写入的援用指向的对象是否和该Reference类型数据在不同的Region(其余收集器:查看老年代对象是否援用了新生代对象);

如果不同,通过CardTable把相干援用信息记录到援用指向对象的所在Region对应的Remembered Set中;

当进行垃圾收集时,在GC根节点的枚举范畴退出Remembered Set;就能够保障不进行全局扫描,也不会有脱漏。

7.7 G1垃圾回收器的回收过程

G1 GC的垃圾回收过程次要包含如下三个环节:

  • 年老代GC(Young GC)
  • 老年代并发标记过程(Concurrent Mark)
  • 混合回收(Mixed GC)

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

顺时针,YOUNG GC->YOUNG GC+Concurrent Mark->Mixed GC程序,进行垃圾回收。

应用程序分配内存,当年老代的Eden区用尽时开始年老代回收过程;G1的年老代收集阶段是一个并行的独占式收集器。在年老代回收期,G1 GC暂停所有应用程序线程,启动多线程执行年老代回收。而后从年老代区间挪动存活对象到Survivor区间或者老年区间,也有可能是两个区间都会波及。

当堆内存应用达到肯定值(默认45%)时,开始老年代并发标记过程。

标记实现马上开始混合回收过程。对于一个混合回收期,G1GC从老年区间挪动存活对象到闲暇区间,这些闲暇区间也就成为了老年代的一部分。和年老代不同,老年代的G1回收器和其余GC不同,G1的老年代回收器不须要整个老年代被回收,一次只须要扫描/回收一小部分老年代的Region就能够了。同时,这个老年代Region是和年老代一起被回收的。

举个例子:一个Web服务器,Java过程最大堆内存为4G,每分钟响应1500个申请,每45秒钟会新调配大概2G的内存。G1会每45秒钟进行一次年老代回收,每31个小时整个堆的使用率会达到45%,会开始老年代并发标记过程,标记实现后开始四到五次的混合回收。

(1)年老代GC

JVM启动时,G1先筹备好Eden区,程序在运行过程中一直创建对象到Eden区,当Eden空间耗尽时,G1会启动一次年老代垃圾回收过程。

YGC时,首先G1进行应用程序的执行(Stop-The-World),G1创立回收集(Collection Set),回收集是指须要被回收的内存分段的汇合,年老代回收过程的回收集蕴含年老代Eden区Survivor区所有的内存分段。

而后开始如下回收过程:

  • 扫描根

根是指static变量指向的对象,正在执行的办法调用链条上的局部变量等。根援用连同RSet记录的内部援用作为扫描存活对象的入口。

  • 更新RSet

解决dirty card queue(见备注)中的card,更新RSet。此阶段实现后,RSet能够精确的反映老年代对所在的内存分段中对象的援用

  • 解决RSet

辨认被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。

  • 复制对象。

此阶段,对象树被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到o1d区中空的内存分段。如果Survivor空间不够,Eden空间的局部数据会间接降职到老年代空间。

  • 解决援用

解决Soft,Weak,Phantom,Final,JNI Weak 等援用。最终Eden空间的数据为空,GC进行工作,而指标内存中的对象都是间断存储的,没有碎片,所以复制过程能够达到内存整理的成果,缩小碎片。

(2)并发标记过程

  • 初始标记阶段:标记从根节点间接可达的对象。这个阶段是STW的,并且会触发一次年老代GC。
  • 根区域扫描(Root Region Scanning):G1 GC扫描survivor区间接可达的老年代区域对象,并标记被援用的对象。这一过程必须在youngGC之前实现。
  • 并发标记(Concurrent Marking):在整个堆中进行并发标记(和应用程序并发执行),此过程可能被youngGC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立刻回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
  • 再次标记(Remark):因为应用程序继续进行,须要修改上一次的标记后果。是STW的。G1中采纳了比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。
  • 独占清理(cleanup,STW):计算各个区域的存活对象和GC回收比例,并进行排序,辨认能够混合回收的区域。为下阶段做铺垫。是STW的。这个阶段并不会实际上去做垃圾的收集
  • 并发清理阶段:辨认并清理齐全闲暇的区域。

(3)混合回收

当越来越多的对象降职到老年代Old region时,为了防止堆内存被耗尽,虚构机会触发一个混合的垃圾收集器,即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分的Old Region。这里须要留神:是一部分老年代,而不是全副老年代。能够抉择哪些Old Region进行收集,从而能够对垃圾回收的耗时工夫进行管制。也要留神的是Mixed GC并不是Full GC。

并发标记完结当前,老年代中百分百为垃圾的内存分段被回收了,局部为垃圾的内存分段被计算了进去。默认状况下,这些老年代的内存分段会分8次(能够通过-XX:G1MixedGCCountTarget设置)被回收。

混合回收的回收集(Collection Set)包含八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年老代回收的算法齐全一样,只是回收集多了老年代的内存分段。具体过程请参考下面的年老代回收过程。

因为老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例越高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,

-XX:G1MixedGCLiveThresholdPercent,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收。如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会破费更多的工夫。

混合回收并不一定要进行8次。有一个阈值-XX:G1HeapWastePercent,默认值为10%,意思是容许整个堆内存中有10%的空间被节约,意味着如果发现能够回收的垃圾占堆内存的比例低于10%,则不再进行混合回收。因为GC会破费很多的工夫然而回收到的内存却很少。

(4)Full GC

G1的初衷就是要防止Full GC的呈现。然而如果上述形式不能失常工作,G1会进行应用程序的执行(Stop-The-World),应用b的内存回收算法进行垃圾回收,性能会十分差,应用程序进展工夫会很长。

要防止Full GC的产生,一旦产生须要进行调整。什么时候会产生Full GC呢?比方堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到Full GC,这种状况能够通过增大内存解决。
导致G1Full GC的起因可能有两个:

  • Evacuation的时候没有足够的to-space来寄存降职的对象;
  • 并发处理过程实现之前空间耗尽。

7.8 G1回收的优化倡议

从oracle官网走漏进去的信息可获知,回收阶段(Evacuation)其实本也有想过设计成与用户程序一起并发执行,但这件事件做起来比较复杂,思考到G1只是回一部分Region,进展工夫是用户可管制的,所以并不迫切去实现,而抉择把这个个性放到了G1之后呈现的低提早垃圾收集器(即ZGC)中。另外,还思考到G1不是仅仅面向低提早,进展用户线程可能最大幅度提高垃圾收集效率,为了保障吞吐量所以才抉择了齐全暂停用户线程的实现计划。

年老代大小

  • 防止应用-Xmn或-XX:NewRatio等相干选项显式设置年老代大小
  • 固定年老代的大小会笼罩

暂停工夫指标暂停工夫指标不要太过严苛

  • G1 GC的吞吐量指标是90%的应用程序工夫和10%的垃圾回收工夫
  • 评估G1GC的吞吐量时,暂停工夫指标不要太严苛。指标太过严苛示意你违心接受更多的垃圾回收开销,而这些会间接影响到吞吐量。

八、垃圾回收器总结

截止JDK1.8,一共有7款不同的垃圾收集器。每一款的垃圾收集器都有不同的特点,在具体应用的时候,须要依据具体的状况选用不同的垃圾收集器。

GC倒退阶段:Serial=> Parallel(并行)=> CMS(并发)=> G1 => ZGC

不同厂商、不同版本的虚拟机实现差距比拟大。HotSpot虚拟机在JDK7/8后所有收集器及组合如下图

怎么抉择垃圾回收器

Java垃圾收集器的配置对于JVM优化来说是一个很重要的抉择,抉择适合的垃圾收集器能够让JVM的性能有一个很大的晋升。怎么抉择垃圾收集器?

  • 优先调整堆的大小让JVM自适应实现。
  • 如果内存小于100M,应用串行收集器。
  • 如果是单核、单机程序,并且没有进展工夫的要求,串行收集器。
  • 如果是多CPU、须要高吞吐量、容许进展工夫超过1秒,抉择并行或者JVM本人抉择。
  • 如果是多CPU、谋求低进展工夫,需疾速响应(比方提早不能超过1秒,如互联网利用),应用并发收集器。
  • 官网举荐G1,性能高。当初互联网的我的项目,根本都是应用G1。

最初须要明确一个观点:

  • 没有最好的收集器,更没有万能的收集器。
  • 调优永远是针对特定场景、特定需要,不存在一劳永逸的收集器。

九、GC日志剖析

通过浏览GC日志,咱们能够理解Java虚拟机内存调配与回收策略。
内存调配与垃圾回收的参数列表

  • -XX:+PrintGC输入GC日志。相似:-verbose:gc
  • -XX:+PrintGcDetails输入GC的具体日志
  • -XX:+PrintGcTimestamps 输入GC的工夫戳(以基准工夫的模式)
  • -XX:+PrintGCDatestamps 输入GC工夫戳(以日期的模式,如2013-05-04T21:53:59.234+0800)
  • -XX:+PrintHeapAtGC在进行GC的前后打印出堆的信息
  • -Xloggc:../logs/gc.1og日志文件的输入门路

verbose:gc

关上GC日志

-verbose:gc

这个只会显示总的GC堆的变动,如下:

参数解析

PrintGCDetails

关上GC日志

-verbose:gc -XX:+PrintGCDetails

输出信息如下

参数解析

补充

  • [GC"和"[Full GC"阐明了这次垃圾收集的进展类型,如果有"Full"则阐明GC产生了"stop The World"。
  • 应用Serial收集器在新生代的名字是Default New Generation,因而显示的是"[DefNew"。
  • 应用ParNew收集器在新生代的名字会变成"[ParNew",意思是"Parallel New Generation"。
  • 应用Parallel scavenge收集器在新生代的名字是”[PSYoungGen"。
  • 老年代的收集和新生代情理一样,名字也是收集器决定的。
  • 应用G1收集器的话,会显示为"garbage-first heap"。

Allocation Failure表明本次引起GC的起因是因为在年老代中没有足够的空间可能存储新的数据了。

[PSYoungGen:5986K->696K(8704K)]5986K->704K(9216K)中括号内:GC回收前年老代大小,回收后大小,(年老代总大小)括号外:GC回收前年老代和老年代大小,回收后大小,(年老代和老年代总大小)

user代表用户态回收耗时,sys内核态回收耗时,rea理论耗时。因为多核的起因,工夫总和可能会超过real工夫

Young GC图片

Full GC图片

GC回收举例

public class GCUseTest {    static final Integer _1MB = 1024 * 1024;    public static void main(String[] args) {        byte [] allocation1, allocation2, allocation3, allocation4;        allocation1 = new byte[2 *_1MB];        allocation2 = new byte[2 *_1MB];        allocation3 = new byte[2 *_1MB];        allocation4 = new byte[4 *_1MB];    }}

咱们设置JVM启动参数

-Xms10m -Xmx10m -XX:+PrintGCDetails

首先咱们会将3个2M的数组寄存到Eden区,而后前面4M的数组来了后,将无奈存储,因为Eden区只剩下2M的残余空间了,那么将会进行一次Young GC操作,将原来Eden区的内容,寄存到Survivor区,然而Survivor区也寄存不下,那么就会间接升级存入Old 区。

而后咱们将4M对象存入到Eden区中:

罕用的日志剖析工具有:GCViewer、GCEasy、GCHisto、GCLogViewer、Hpjmeter、garbagecat等。

十、垃圾回收器的新倒退

GC依然处于飞速发展之中,目前的默认选项G1 GC在一直的进行改良,很多咱们原来认为的毛病,例如串行的Full GC、Card Table扫描的低效等,都曾经被大幅改良,例如,JDK10当前,Fu11GC曾经是并行运行,在很多场景下,其体现还略优于ParallelGC的并行Full GC实现。

即便是Serial GC,尽管比拟古老,然而简略的设计和实现未必就是过期的,它自身的开销,不论是GC相干数据结构的开销,还是线程的开销,都是十分小的,所以随着云计算的衰亡,在serverless等新的利用场景下,Serial GC找到了新的舞台。

比拟可怜的是CMS GC,因为其算法的实践缺点等起因,尽管当初还有十分大的用户群体,但在JDK9中曾经被标记为废除,并在JDK14版本中移除。

Epsilon:A No-Op GarbageCollector(Epsilon垃圾回收器,"No-Op(无操作)"回收器)

ZGC:A Scalable Low-Latency Garbage Collector(Experimental)(ZGC:可伸缩的低提早垃圾回收器,处于实验性阶段)

当初G1回收器已成为默认回收器好几年了。咱们还看到了引入了两个新的收集器:ZGC(JDK11呈现)和Shenandoah(Open JDK12)

主打特点:低进展工夫

Open JDK12的Shenandoah GC

Open JDK12的Shenandoah GC:低进展工夫的GC(实验性)

Shenandoah,无疑是泛滥GC中最孤单的一个。是第一款不禁Oracle公司团队领导开发的Hotspot垃圾收集器。不可避免的受到官网的排斥。比方号称openJDK和OracleJDk没有区别的Oracle公司仍回绝在OracleJDK12中反对Shenandoah。

Shenandoah垃圾回收器最后由RedHat进行的一项垃圾收集器钻研我的项目Pauseless GC的实现,旨在针对JVM上的内存回收实现低进展的需要。在2014年奉献给OpenJDK。

Red Hat研发Shenandoah团队对外声称,Shenandoah垃圾回收器的暂停工夫与堆大小无关,这意味着无论将堆设置为200MB还是200GB,99.9%的指标都能够把垃圾收集的进展工夫限度在十毫秒以内。不过理论使用性能将取决于理论工作堆的大小和工作负载。

这是RedHat在2016年发表的论文数据,测试内容是应用Es对200GB的维基百科数据进行索引。从后果看:

进展工夫比其余几款收集器的确有了质的飞跃,但也未实现最大进展工夫管制在十毫秒以内的指标。
而吞吐量方面呈现了显著的降落,总运行工夫是所有测试收集器里最长的。

总结

  • shenandoah GC的弱项:高运行累赘下的吞吐量降落。
  • shenandoah GC的强项:低延迟时间。

革命性的ZGC

ZGC与Shenandoah指标高度类似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都能够把垃圾收集的停颇工夫限度在十毫秒以内的低提早。

《深刻了解Java虚拟机》一书中这样定义ZGC:ZGC收集器是一款基于Region内存布局的,(临时)不设分代的,应用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低提早为首要指标的一款垃圾收集器。

ZGC的工作过程能够分为4个阶段:并发标记 - 并发准备重调配 - 并发重调配 - 并发重映射 等。

ZGC简直在所有中央并发执行的,除了初始标记的是STW的。所以进展工夫简直就消耗在初始标记上,这部分的理论工夫是非常少的。

进展工夫比照

尽管ZGC还在试验状态,没有实现所有个性,但此时性能曾经相当亮眼,用“令人震惊、革命性”来形容,不为过。
将来将在服务端、大内存、低提早利用的首选垃圾收集器。

JDK14之前,ZGC仅Linux才反对。

只管许多应用ZGc的用户都应用类Linux的环境,但在Windows和macos上,人们也须要ZGC进行开发部署和测试。许多桌面利用也能够从ZGC中受害。因而,ZGC个性被移植到了Windows和macos上。

当初mac或Windows上也能应用ZGC了,示例如下:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC