JVM垃圾回收器

204 阅读6分钟

一、GC分类与性能指标

1.按线程数分类

可以分为串行垃圾回收器和并行垃圾回收器。

应用场景:

串行垃圾回收器:单CPU处理器或较小的应用内存等硬件平台不是特别优越的场景,

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

2.按工作模式分

可以分为并发式垃圾回收器和独占式垃圾回收器。

并发式垃圾回收器:与应用程序线程交替工作,以尽 可能减少应用程序的停顿时间。

独占式垃圾回收器:独占式垃圾回收器一旦运行,就停止应用程序中的所有用户线程,直到垃圾回收过程完全结束。

3.按碎片处理方式

可分为压缩式垃圾回收器和非压缩式垃圾回收器

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

非压缩式垃圾回收器:不进行这步操作。

4.按工作的内存区间

可以分为年轻代垃圾回收器和老年代垃圾回收器。

5.评估GC的性能指标

吞吐量:运行用户代码的时间占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)

垃圾收集开销:吞吐量的补数,垃圾收集所用时间与总运行时间的比例。

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

收集频率:相对于应用程序的执行,收集操作发生的频率。

内存占用:Java堆区所占的内存大小。

快速:一个对象从诞生到被回收所经历的时间。

简单来说,主要着重两点:吞吐量和暂停时间。

一个垃圾算法只能针对两个目标之一(即只专注较大吞吐量或最小暂停时间),或尝试找到一个二者的折中。

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

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

CMS -> G1(目前) -> ZGC(未来)

JDK8 ==> 新生代Parallel GC 老年代 Parallel Old GC

JDK9 ==> G1 GC 皆可以回收新生代 又可以回收老年代

三、Serial回收器:串行回收

四、ParNew回收器:并行回收

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

六、CMS(Concurrent-Mark-Sweep 并发-标记清除算法)回收器:低延迟与并发收集

这款收集器时HotSpot虚拟机中第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程“同时”工作。

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

JDK5 中使用CMS来收集老年代的时候,新生代只能选择 ParNew或者Serial收集器的一个。

初始标记(STW 只标记 GCRoots直接关联对象) ==> 并发标记(用户线程同时执行,标记深层次的关联对象) ==> 重新标记(STW,由于在并发标记时,用户线程也在执行,导致某些对象变成垃圾或者不是垃圾,所以需要重新修正标记)==> 并发清理 ==> 重置线程

由于并发标记和并发清理最耗时,但是 由于是与用户线程并发执行。所以是低延迟的。

由于使用标记清除算法,导致内存碎片,所以为什么不适用 标记整理算法呢?

因为如果使用压缩算法,那么在 并发清除阶段,由于改变 对象内存地址,就会影响用户线程的正确执行。

弊端:

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

2)CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。

3)CMS收集器无法处理浮动垃圾(在重新标记时,上一步并发标记时,用户线程使得某些对象变成垃圾或不是垃圾,所以新产生的这些垃圾就叫做浮动垃圾)。

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

吞吐量:新生代 parallel gc 老年代parallel old gc

低延迟:CMS gc

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

因为G1是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续)。使用不同的区域表示 Eden。幸存者0区,幸存者1区,老年代等。

G1 GC 有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

由于这种方式的侧重点在于回收垃圾最大量的区间(Region),所以给G1给一个名字:垃圾优先。

缺点:G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。

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

常见操作步骤

第一步:开启G1垃圾收集器 -XX:+UseG1GC

第二步:设置堆的最大内存 -XX:G1HeapRegisonSize(设置每个Region 的大小)

第三步:设置最大的停顿时间-XX:MaxGCPauseMillis(默认是200ms)

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

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

分区 Region:化整为零

使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。可以通过-XX:MaxHeapRegionSize 设定。所有的Region大小相同,且在JVM生命周期内不会被改变。

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

垃圾回收过程:

年轻代GC ===> 年轻代GC+并发标记过程 ===> 混合回收

\