JVMJVM内存结构之——垃圾收集器(并发与并行/ GC性能指标/ 垃圾收集器组合关系/ CMS收集器/ G1收集器)

Posted 超级码里喵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVMJVM内存结构之——垃圾收集器(并发与并行/ GC性能指标/ 垃圾收集器组合关系/ CMS收集器/ G1收集器)相关的知识,希望对你有一定的参考价值。

目录


1. System.gc()方法

在默认情况下,System.gc()会显示直接触发Full GC,同时对老年代和新生代进行回收。而一般情况下,垃圾回收应该是自动进行的,无需手工触发。
不建议手动调用 System.gc() 因为容易导致系统stw问题。

2. 并行与并发概念

2.1 并发的概念

1.在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理器上运行
2.并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换。由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行

2.2 并行的概念

1.当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,我们称之为并行(Parallel)
2.其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行

3. 垃圾回收器并行与串行区别

1.并行(Parallel):指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
如ParNew、Parallel Scavenge、Parallel Old

2.串行(Serial)
相较于并行的概念,单线程执行。
如果内存不够,则程序暂停,启动JVM垃圾回收器进行垃圾回收(单线程)

垃圾回收器 经典七款垃圾收集器 比如 CMS、并行收集器 串行收集器等。
垃圾回收算法: 标记清除算法 标记复制算法 标记整理算法; 清理堆内存垃圾
可达分析算法:判断该对象是否为垃圾对象 引用计数法(java中没有使用)

4. 并行收集器(parallel) 与并发收集器(concurrent)区别

并行收集器(parallel) :多条垃圾收集线程同时进行工作,此时用户线程处于等待状态
并发收集器(concurrent):指多条垃圾收集线程与用户线程同时进行(但不一定是并行的,有可能交替进行工作)
如cms(老年代)/G1(整堆)就是典型的并发收集器

GC线程清理垃圾对象时,用户线程暂停。

串行收集器:只会开启一个GC线程清理堆内存垃圾,如果堆内存空间
非常大(3-4gb垃圾对象时),一个GC线清理堆内存垃圾 忙不过来最终
可能会导致用户线程暂停的时间会非常长。
并行收集器:会开启多个GC线程同时清理堆内存垃圾(还是会暂停我们用户线程)
降低用户线程暂停的时间。
并发收集器:GC线程和用户线程交替运行

5. 评估 GC 的性能指标

1.吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间 = 程序的运行时间 + 内存回收的时间)
2.垃圾收集开销:垃圾收集所用时间与总运行时间的比例。
3.暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间。
4.收集频率:相对于应用程序的执行,收集操作发生的频率。
5.内存占用:Java堆区所占的内存大小。
6.快速:一个对象从诞生到被回收所经历的时间。

吞吐量、暂停时间、内存占用这三者共同构成一个“不可能三角”。三者总体的表现会随着技术进步而越来越好。一款优秀的收集器通常最多同时满足其中的两项。
核心:评估 GC 的性能指标 核心:(吞吐量和暂停用户线程时间指标使用非常多的。)

5.1 吞吐量(throughput)

1.吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间)
2.比如:虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
这种情况下,应用程序能容忍较高的暂停时间,因此,高吞吐量的应用程序有更长的时间基准,快速响应是不必考虑的

5.2 暂停时间(pause time)

1.“暂停时间”是指一个时间段内应用程序线程暂停,让GC线程执行的状态。
例如,GC期间10毫秒的暂停时间意味着在这10毫秒期间内没有应用程序线程是活动的
2. 暂停时间优先,意味着尽可能让单次STW的时间最短:50ms*10=0.5s,但是总的GC时间可能会长
暂停时间越短,但是GC回收频率越高

5.3 吞吐量 vs 暂停时间区别

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

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

3.不幸的是”高吞吐量”和”低暂停时间”是一对相互竞争的目标(矛盾)。

4.因为如果选择以吞吐量优先,那么必然需要降低内存回收的执行频率,但是这样会导致GC需要更长的暂停时间来执行内存回收。

5.相反的,如果选择以低延迟优先为原则,那么为了降低每次执行内存回收时的暂停时间,也只能频繁地执行内存回收,但这又引起了年轻代内存的缩减和导致程序吞吐量的下降。
在设计(或使用)GC算法时,我们必须确定我们的目标:一个GC算法只可能针对两个目标之一(即只专注于较大吞吐量或最小暂停时间),或尝试找到一个二者的折衷。

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

6. 垃圾收集器发展历史

7. 7款经典的垃圾收集器

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

8. 经典的垃圾收集器关系

新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial old、Parallel old、CMS;
整堆收集器:G1(JD9默认)

9. 垃圾收集器组合关系

1.Serial/Serial old备注:新生代和老年代都使用串行(单线程回收垃圾对象)
2.Serial/CMS (JDK9废弃)
3.ParNew/Serial Old (JDK9废弃) ParNew( Par Parallel 简写的)new 新生代中
4.ParNew/CMS
5.Parallel Scavenge/Serial Old (可能被废弃)
新生代中使用并行收集器、老年代中使用串行收集器
6. Parallel Scavenge/Parallel Old(JDK8默认)
新生代和老年代中都是并行收集器(开启了多个线程)
7.G1(JDK9默认)
核心点:堆内存 新生代(哪些收集器)、老年代(哪些收集器)

10. 串行垃圾收集器Serial、Serial old

1.Serial收集器是最基本、历史最悠久的垃圾收集器了。JDK1.3之前回收新生代唯一的选择。
2.Serial收集器作为HotSpot中Client模式下的默认新生代垃圾收集器。
3.Serial收集器采用复制算法、串行回收和"Stop-the-World"机制的方式执行内存回收。

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

  1. Serial Old是运行在Client模式下默认的老年代的垃圾回收器,Serial Old在Server模式下主要有两个用途:
    1.1与新生代的Parallel Scavenge配合使用
    1.2作为老年代CMS收集器的后备垃圾收集方案
    这个收集器是一个单线程的收集器,“单线程”的意义:它只会使用一个CPU(串行)或一条收集线程去完成垃圾收集工作。更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)。

10.1 串行收集器优缺点

1.为了消除或减少工作线程因内存回收而导致的停顿,HotSpot虚拟机开发团队在JDK 1.3之后的Java发展历程中研发出了各种其他的优秀收集器,这些将在稍后介绍。但是这些收集器的诞生并不意味着Serial收集器已经“老而无用”,实际上到现在为止,它依然是HotSpot虚拟机运行在Client模式下的默认的新生代收集器。
2.它也有着优于其他收集器的地方:简单而高效(与其他收集器的单线程相比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得更高的单线程收集效率。
3.在用户的桌面应用场景中,分配给虚拟机管理的内存一般不会很大,收集几十兆甚至一两百兆的新生代(仅仅是新生代使用的内存,桌面应用基本不会再大了),停顿时间完全可以控制在几十毫秒最多一百毫秒以内,只要不频繁发生,这点停顿时间可以接收。
4.所以,Serial收集器对于运行在Client模式下的虚拟机来说是一个很好的选择。
在HotSpot虚拟机中,使用-XX:+UseSerialGC参数可以指定年轻代和老年代都使用串行收集器。
等价于新生代用Serial GC,且老年代用Serial Old GC

10.2 使用串行收集器

-XX:+PrintCommandLineFlags -XX:+UseSerialGC

11. ParNew 回收器:并行回收

ParNew收集器就是Serial收集器的多线程版本,它也是一个新生代收集器。 Par是Parallel的缩写,New:只能处理新生代GC
1.ParNew (新生代) ParNew收集器在年轻代中同样也是采用复制算法、
"Stop-the-World"机制
2.对于新生代,回收次数频繁,使用并行方式高效。
3.新生代使用并行回收(多个GC线程 标记复制算法)、Serial old 串行(标记整理算法)回收老年代

1.ParNew收集器除了使用多线程收集外,其他与Serial收集器相比并无太多创新之处,但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关的重要原因是,除了Serial收集器外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作,CMS收集器是JDK 1.5推出的一个具有划时代意义的收集器,具体内容将在稍后进行介绍。

2.ParNew 收集器在单CPU的环境中绝对不会有比Serial收集器有更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越。

3.在多CPU环境下,随着CPU的数量增加,它对于GC时系统资源的有效利用是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多的情况下可使用-XX:ParallerGCThreads参数设置。
总结:
对于新生代,回收次数频繁,使用并行方式高效。
对于老年代,回收次数少,使用串行方式节省资源。(CPU并行需要切换线程,串行可以省去切换线程的资源)

注意:使用ParNew最好使用到多核cpu下,如果在单核cpu情况下使用 ParNew收集器
效率比一定比Serial高。

什么情况下使用 串行收集器?并行收集器呢?
单核cpu下 配置 并行收集器 新生代ParNew–
单核cpu----串行收集器
多核cpu —推荐并行收集器

11.1 设置 ParNew 垃圾收集器

1.在程序中,开发人员可以通过选项"-XX:+UseParNewGC"手动指定使用ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。
2.-XX:ParallelGCThreads限制线程数量,默认开启和CPU核数相同的线程数。

JDK9环境下设置: -XX:+UseParNewGC报错:

12. Parallel 回收器:吞吐量优先

  1. HotSpot的年轻代中除了拥有ParNew(新生代)收集器是基于并行回收的以外,Parallel Scavenge收集器同样也采用了复制算法、并行回收;
  2. Parallel 与ParNew(新生代)收集器区别:
    2.1Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先的垃圾收集器。
    2.2自适应调节策略也是Parallel Scavenge与ParNew一个重要区别。(动态调整内存分配情况,以达到一个最优的吞吐量或低延迟)
    2.3高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务
    3.Parallel收集器在JDK1.6时提供了用于执行老年代垃圾收集的Parallel Old收集器,用来代替老年代的Serial Old收集器,Parallel Old收集器采用了标记-压缩算法,但同样也是基于并行回收和"Stop-the-World"机制。
    JDK8 默认的Parallel收集器。

12.1 Parallel Scavenge 回收器参数设置

1.-XX:+UseParallelGC 手动指定年轻代使用Parallel并行收集器执行内存回收任务。
2.-XX:+UseParallelOldGC:手动指定老年代都是使用并行回收收集器。
3.分别适用于新生代和老年代
4.新生代使用UseParallelGC 老年代UseParallelOldGC
5.核心参数:
-XX:ParallelGCThreads:设置年轻代并行收集器的线程数。一般地,最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能。
6.在默认情况下,当CPU数量小于8个,ParallelGCThreads的值等于CPU数量。
当CPU数量大于8个,ParallelGCThreads的值等于3+[5*CPU_Count]/8]
7.-XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒。
为了尽可能地把停顿时间控制在XX:MaxGCPauseMillis 以内,收集器在工作时会调整Java堆大小或者其他一些参数。
对于用户来讲,停顿时间越短体验越好。但是在服务器端,我们注重高并发,整体的吞吐量。所以服务器端适合Parallel,进行控制。
该参数使用需谨慎。
8.-XX:GCTimeRatio垃圾收集时间占总时间的比例,即等于 1 / (N+1) ,用于衡量吞吐量的大小。5% GC 5
取值范围(0, 100)。默认值99,也就是垃圾回收时间占比不超过1。
与前一个-XX:MaxGCPauseMillis参数有一定矛盾性,STW暂停时间越长,Radio参数就容易超过设定的比例。
9.-XX:+UseAdaptiveSizePolicy 设置Parallel Scavenge收集器具有自适应调节策略
在这种模式下,年轻代的大小、Eden和Survivor的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。
在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量(GCTimeRatio)和停顿时间(MaxGCPauseMillis),让虚拟机自己完成调优工作。

13. CMS收集器

13.1 CMS收集器概述(低延迟)

1.在JDK1.5时期,Hotspot推出了一款在强交互应用中(就是和用户打交道的引用)几乎可认为有划时代意义的垃圾收集器:CMS(Concurrent-Mark-Sweep)收集器,这款收集器是HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作。(并发收集器)
2.CMS收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间。停顿时间越短(低延迟)就越适合与用户交互的程序,良好的响应速度能提升用户体验。
3.目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
4.CMS的垃圾收集算法采用标记-清除算法,并且也会"Stop-the-World"
5.不幸的是,CMS作为老年代的收集器,却无法与JDK1.4.0中已经存在的新生代收集器Parallel Scavenge配合工作(因为实现的框架不一样,没办法兼容使用),所以在JDK1.5中使用CMS来收集老年代的时候,新生代只能选择ParNew或者Serial收集器中的一个。
在G1出现之前,CMS使用还是非常广泛的。一直到今天,仍然有很多系统使用CMS GC。
6.JDK9开始同时不推荐使用CMS收集器、JDK14将CMS收集器删除

13.2 CMS收集器原理

CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即
1.初始标记阶段;
2.并发标记阶段;
3.重新标记阶段;
4.并发清除阶段;
(涉及STW的阶段主要是:初始标记 和 重新标记)

1.初始标记(Initial-Mark)阶段:在这个阶段中,程序中所有的工作线程都将会因为“Stop-the-World”机制而出现短暂的暂停,这个阶段的主要任务仅仅只是标记出GC Roots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快。

2.并发标记(Concurrent-Mark)阶段:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行。

3.重新标记(Remark)阶段:由于在并发标记阶段中,程序的工作线程会和垃圾收集线程同时运行或者交叉运行,因此为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始标记阶段稍长一些,并且也会导致“Stop-the-World”的发生,但也远比并发标记阶段的时间短。

4.并发清除(Concurrent-Sweep)阶段:此阶段清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。(标记清除算法)由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的

13.3 CMS收集器细节分析

1.尽管CMS收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“Stop-the-World”,只是尽可能地缩短暂停时间。
2.由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作线程,所以整体的回收是低停顿的。
另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure” 失败,这时虚拟机将启动后备预案:临时启用Serial old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
CMS 清理垃圾时 并发清除时 用户线程与GC线程同时运行
3.CMS收集器的垃圾收集算法采用的是标记清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer)技术,而只能够选择空闲列表(Free List)执行内存分配。

13.4 CMS 收集器 在什么时候触发回收垃圾呢?

  1. 其他收集器 堆内存空间 不足时 触发 GC回收。
  2. CMS 收集器 清理垃圾时 提前根据堆阈值使用(70) 提前清理堆内存垃圾
    并发收集器 用户线程与GC线程同时运行

13.5 CMS 优缺点

优点:
1.并发标记(用户线程与GC线程同时运行)
2.低延迟;(暂停用户线程时间变得更加短)
缺点:

  1. 会产生碎片化的问题(并发清除 GC和用户线程同时执行 标记清除算法)
  2. CMS收集器对CPU资源非常敏感 在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
  3. CMS收集器无法处理浮动垃圾,并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记(第一阶段没有扫描到该GCRoot),最终会导致这些新产生的垃圾对象没有被及时回收,从而只能在下一次执行GC时释放这些之前未被回收的内存空间。
    CMS(老年代) —并行收集器(parnew)

13.6 CMS 参数设置

1.-XX:+UseConcMarkSweepGC:手动指定使用CMS收集器执行内存回收任务。
开启该参数后会自动将-XX:+UseParNewGC打开。即:ParNew(Young区)+CMS(Old区)+Serial Old(Old区备选方案)的组合。
2.-XX:CMSInitiatingOccupanyFraction:设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。 JDK5及以前版本的默认值为68,即当老年代的空间使用率达到68%时,会执行一次CMS回收。JDK6及以上版本默认值为92%
如果内存增长缓慢,则可以设置一个稍大的值,大的阀值可以有效降低CMS的触发频率,减少老年代回收的次数可以较为明显地改善应用程序性能。反之,如果应用程序内存使用率增长很快,则应该降低这个阈值,以避免频繁触发老年代串行收集器。因此通过该选项便可以有效降低Full GC的执行次数。
3. -XX:+UseCMSCompactAtFullCollection:用于指定在执行完Full GC后对内存空间进行压缩整理,以此避免内存碎片的产生。不过由于内存压缩整理过程无法并发执行,所带来的问题就是停顿时间变得更长了。CMS 并发清除-
4.-XX:CMSFullGCsBeforeCompaction:设置在执行多少次Full GC后对内存空间进行压缩整理。
5.-XX:ParallelCMSThreads:设置CMS的线程数量。
CMS默认启动的线程数是 (ParallelGCThreads + 3) / 4,ParallelGCThreads是年轻代并行收集器的线程数,可以当做是 CPU 最大支持的线程数。当CPU资源比较紧张时,受到CMS收集器线程的影响,应用程序的性能在垃圾回收阶段可能会非常糟糕。

13.7 CMS 收集器版本优化

  1. JDK9中开始不推荐使用CMS收集器;使用g1(整堆)
  2. JDK14 删除CMS垃圾回收器(JEP363)移除了CMS垃圾收集器
    JDK开始官方推荐学习 G1收集器

14. CMS收集器常见面试题

14.1 CMS回收是在什么时候触发

提前回收 根据 用户设置 使用堆内存阈值

14.2 为什么 CMS 不采用标记-压缩算法而是标记清除算法?

因为在并发清除时,用户线程和GC线程是同时在执行 没有发生stw问题 而使用标记整理算法时,会发生移动对象,对象引用内存地址,所以不能够使用标记整理算法而是标记清除算法(不会移动对象)

14.3 CMS 收集器如何处理浮动垃圾问题?

GC与用户线程同时运行 并发标记、并发清除

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

  1. 可能出现“Concurrent Mode Failure"失败而导致另一次Full GC的产生
  2. 放到下一次GC清理。

14.4 CMS回收停顿用户线程了几次,为什么要停顿用户线程两次?

两次:

  1. 初始标记;
  2. 重新修正;
    重新修正暂停用户线程的时间比 初始标记初始标记 长

14.5 为什么配置了CMS GC收集器,却触发了Full GC?

1.大对象分配时,年轻代放不下,直接去老年代,结果老年代也放不下。
2.CMS GC失败(concurrent mode failure导致)
3.老年代碎片化的问题严重—标记整理算法

15. 什么是G1

G1收集器(Garbage-First Garbage Collector) 整堆收集。
G1收集器是一款在server端运行的垃圾收集器,专门针对于拥有多核处理器和大内存的机器,在JDK 7u4版本发行时被正式推出,在JDK9中更被指定为默认GC收集器。它满足高吞吐量的同时满足GC停顿的时间尽可能短。

  1. 因为G1是一个并行/并发回收器,它把堆内存分割为很多不相关的区域(Region) (物理上 不连续的)。使用不同的Region来表示Eden、幸存者0(S0)区,幸存者(S1)1区,老年代等。
  2. 由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以我们给G1一个名字:垃圾优先(Garbage First) 。
  3. G1 GC有计划地避免在整个Java 堆中进行全区域的垃圾收集。G1跟踪各个Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
  4. G1收集器使用的是 整体上使用标记整理 两个Region 之间 标记复制算法。

15.1 G1收集器发展历程

1.2004发表:G1的第一篇paper
2.2012年才在jdk1.7u4中可用
3.2017 年jdk9中将G1变成默认的垃圾收集器以替代CMS。

15.2 G1收集器分区划分

1.使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。2048MB 每个独立区间1
2.可以通过-XXG1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
3.虽然还保留着新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑_上的连续。
一个region(分区)只能属于一个角色,有可能为eden区、S区、老年代等, E表示为Eden区、S区表示为S1,S0区,老年代O区 空白的表示为未使用的分配的内存。
H区存放巨型对象

15.2.1 为什么G1收集器需要设计巨型对象

  1. 在G1收集器中也有一个新的内存区域,称作为:Humongous (H)区(巨型对象),主要存放一些比较大的对象,一个对象大于region的一半时,称之为巨型对象,G1不会对巨型对象进行拷贝,回收时会考虑优先回收。
    2.在以前收集器中,如果是一个大对象是直接放入到老年代中,而触发老年代GC不是很频繁,万一该大对象不是非常频繁的使用,则会非常浪费我们堆内存,为了解决这个问题在G1收集器专门弄一个H区存放巨型对象,如果一个H区装不下的情况下,则会寻找连续H区存储,如果还没有足够的空间,有可能会引发FULLGC.

15.2.2 G1收集器参数设置

-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G,-XX:MaxGCPauseMillis=200设置GC的最大暂停时间为200ms。如果我们需要调优,在内存大小一定的情况下,我们只需要修改最大暂停时间即可。

15.2.3 G1收集器回收的细节

G1收集器回收过程分为三个环节:

  1. 年轻代GC(Young GC )
  2. 新生代和并发标记过程( Concurrent Marking
  3. 混合回收(Mixed GC )----
  4. Full GC
    young gc(新生代) 一> young gc + concurrent mark(新生代+并发标) 一> Mixed GC(混合回收)顺序,进行垃圾回收。

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

2.当堆内存使用达到一定值(默认45%)时,开始老年代并发标记过程。—CMS

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

15.2.4 G1收集器Rset问题(记忆集)

1.概念:
在垃圾收集过程中,会存在一种现象,即跨代引用,在G1中,又叫跨Region引用。如果是年轻代指向老年代的引用我们不用关心,因为即使Minor GC把年轻代的对象清理掉了,程序依然能正常运行,而且随着引用链的断掉,无法被标记到的老年代对象会被后续的Major GC回收

2.如果是老年代指向年轻代的引用,那这个引用在Minor GC阶段是不能被回收掉的,那如何解决这个问题呢?

3.最合理的实现方式自然是记录哪些Region中的老年代的对象有指向年轻代的引用。GC时扫描这些Region就行了。这就是RSet存在的意义。RSet本质上是一种哈希表,Key是Region的起始地址,Value是一个集合,里面存储的元素是卡表的索引号。

4.现代JVM,堆空间通常被划分为新生代和老年代。由于新生代的垃圾收集通常很频繁,如果老年代对象引用了新生代的对象,那么,需要跟踪从老年代到新生代的所有引用,从而避免每次YGC时扫描整个老年代,减少开销。

5.对于HotSpot JVM,使用了卡标记(Card Marking)技术来解决老年代到新生代的引用问题。具体是,使用卡表(Card Table)和写屏障(Write Barrier)来进行标记并加快对GC Roots的扫描。

15.2.5 G1两种回收策略

G1 Young GC(新生代GC)
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。

G1 Mix GC(混合回收)

15.3 G1收集器优缺点

  1. 并行与并发
    并行:G1收集器在回收时,可以实现多个GC线程同时执行 利用CPU多核利用率,但是会让用户线程暂停 触发stw机制。 堆6个GB JDK8环境开启G1 jdk9默认使用
    并发:多个GC与用户线程用时执行,用户线程不会阻塞。
  2. 分代收集原理
    G1收集器,也会分为新生代、老年代, 新生代eden、S0或者S1区域,但是不要求整个eden、S0或者S1区域具有连续性。
    与之前的收集器不同,它可以收集新生代也可以收集老年代。
  3. 空间整合
    之前我们所学习的CMS收集器采用标记清除算法,容易产生碎片化的问题且空间不连续性,而G1收集器划分成n多个不同的采用标记压缩算法,没有产生碎片化的问题。分配大对象的时候,避免FullGC的问题,所以如果堆内存空间比较大,使用G1收集器更加有优势。
  4. 可预测的停顿时间模型 能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
    由于G1收集器采用分区模型,所以G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。G1跟踪各个Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

缺点:小内存的情况下使用cms收集器,大内存的情况下可以使用G1收集器。G1收集器6GB以上

15.4 G1收集器核心配置参数

JDK9已经默认开启了G1收集器,如果在JDK8开启G1收集器。需要配置
-XX:G1HeapRegionSize 设置每个Region的大小。值是2的幂,范围是1MB 到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。 也就是G1收集器最小堆内存应该是2GB以上,最大堆内存64GB
-XX:MaxGCPauseMillis 设置期望达到的最大Gc停顿时间指标 ,默认值是200ms
-XX:ParallelGCThread 设置垃圾回收线程数 最大设置为8
-XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
-XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。
-XX:+UseG1GC 设置开启G1收集器

-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -verbose:gc

以上是关于JVMJVM内存结构之——垃圾收集器(并发与并行/ GC性能指标/ 垃圾收集器组合关系/ CMS收集器/ G1收集器)的主要内容,如果未能解决你的问题,请参考以下文章

JVM之垃圾收集算法与垃圾收集器

JVM虚拟机---垃圾回收器

JVM之垃圾收集器 (GC) 与内存分配策略

JVMJVM系列之垃圾回收

JVM虚拟机---垃圾回收器

垃圾收集器与内存分配策略之篇一:简要概述和垃圾收集算法