查看默认的垃圾收集器

  1. 运行时带上JVM参数-XX: +PrintCommandLineFlags:查看命令行相关参数(包含使用的垃圾收集器) 。
  2. 使用命令行指令: jinfo -flag 相关垃圾回收器参数 进程ID。

GC的性能指标

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

组合关系

image-20201202233303029

  • 红色虚线:在JDK 8时将Serial+CMS、ParNew+Serial Oldd这两个组合声明为废弃(JEP 173),并在JDK 9中完全取消了
    这些组合的支持(JEP214)。
  • 绿色虚线:在jdk14中弃用Paral1el Scavenge 和Serial0ld GC组合(JEP366)
  • 青色虚线:在jdk14中:删除CMS垃圾回收器(JEP 363)

串行回收器

Serial

第一款GC,1999年随jdk1.3发布,负责回收年轻代。HotSpot中Client模式下的默认新生代垃圾收集器。采用复制算法、在HotSpot虚拟机中,使用-XX:+UseSerialGC 指定年轻代和老年代都使用串行收集器。等价于新生代用Serial GC, 且老年代用Serial Old GC。

Serial Old

负责回收老年代,内存回收算法使用的是标记-压缩算法。是运行在Client模式下默认的老年代的垃圾回收器。在Server模式下主要有两个用途: 与新生代的Parallel Scavenge配合使用或作为老年代CMS收集器的后备垃圾收集方案。

串行回收器意味着使用一个CPU或一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop The World)。

工作流程

image-20201203155200633

优势

简单而高效(与其他收集器的单线程比),单CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。一般用于用户的桌面应用等资源有限环境。

并行回收器

ParNew(Parallel New)

负责回收年轻代,相当于Serial的多线程版,在多CPU的环境下,充分利用多CPU、多核心等物理硬件资源优势。采用复制算法。ParNew是很多JVM运行在Server模式下新生代的默认垃圾收集器。

工作流程

image-20201203155249152

在单个CPU的环境下,ParNew收集 器不比Serial收集器更高效。虽然Serial收集器是基于串行回收,但是由于CPU不需要频繁地做任务切换,因此可以有效避免多线程交互过程中产生的-一些额外开销。除Serial外,目前只有ParNew GC能与CMS收集器配合工作。

-XX: +UseParNewGC 指定ParNew收集器执行内存回收任务。它表示年轻代使用并行收集器,不影响老年代。

-XX:ParallelGCThreads 指定线程数量,默认开启和CPU数据相同的线程数。

Parallel Scavenge

jdk1.6后默认使用,负责回收年轻代。采用了复制算法、并行回收。和ParNew收集器不同,Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput),它也被称为吞吐量优先(高吞吐量可以充分利用CPU)的垃圾收集器。自适应调节策略也是Parallel Scavenge 与ParNew一个重要区别。

参数配置:

-XX: +UseParallelGC 指定年轻代使用Parallel。

-XX:+UseParallel0ldGC 指定老年代都是使用并行回收收集器。

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

-XX: ParallelGCThreads设置年轻代并行收集器的线程数。一般地与CPU数量相等,以避免过多的线程数影响垃圾收集性能。

在默认情况下,当CPU数量小于8个, ParallelGCThreads 的值等于CPU数量。当CPU数量大于8个, ParallelGCThreads的值等于3+ [5*CPU_ Count]/8]。

-XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间)。单位是毫秒。

-XX: GCTimeRatio 垃圾收集时间占总时间的比例( 1 / (N+ 1))。用于衡量吞吐量的大小。取值范围(0,100)。默认值99,也就是垃圾回收时间不超过1%。与前一个-XX:MaxGCPauseMillis参数有一 定矛盾性。暂停时间越长,Radio参数就容易超过设定的比例。

-XX: +UseAdaptiveSizePolicy ,设置Parallel Scavenge收集器具有自适应调节策略
在这种模式下,年轻代的大小、Eden和Survivor的比例、 晋升老年代的对象年龄等参数会被自动调整,以达到在堆大小、吞吐量和停顿
时间之间的平衡点。

Parallel Old

负责回收老年代,用来代替老年代的Serial 0ld收集器。采用了标记-压缩算法,基于并行回收和”Stop- the-World”机制。

工作流程

image-20201203155328057

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

并发回收器

CMS(Concurrent Mark Sweep)

负责回收老年代,jdk1.5推出,采用标记-清除算法,并且也会”Stop- the-world”。是HotSpot虚拟机中第一 款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程同时工作低延迟)。缩短垃圾收集时间、用户线程的停顿时。

工作流程

image-20201203152900355

初始标记阶段(Initial-Mark)

会出现“Stop- the-World”,只标记出GCRoots能直接关联到的对象。一旦标记完成之后就会恢复之前被暂停的所有应用线程。由于直接关联对象比较小,所以这里的速度非常快。

并发标记阶段(Concurrent-Mark)

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

重新标记阶段(Remark)

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

并发清除阶段(Concurrent -Sweep)

清理删除掉标记阶段判断的已经死亡的对象,释放内存空间。

  1. CMS无法与JDK 1.4 中已经存在的新生代收集器Paral1el Scavenge配合工作,所以在jdk1. 5中使用时,新生代只能选择ParNew或者Serial收集器中的一个。

  2. 在CMS回收过程中,为确保应用程序用户线程有足够的内存可用。CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收。若CMS运行期间预留的内存无法满足程序需要,就会出现一次”Concurrent Mode Failure“失败,这时虚拟机将启动后备预案Serial 0ld收集器来重新进行老年代的垃圾收集,停顿时间变长了。

  3. jdk9将CMS标记Deprecate(弃用),jdk14将其移除

参数

-XX: +UseConcMarkSweepGC手动指定使用CMS收集器。开启该参数后会自动将-XX: +UseParNewGC打开。 即: ParNew (Young区
用) +CMS (Old区用) +Serial Old的组合。

-XX: CMSlnitiatingOccupanyFraction 设置堆内存使用率的阈值,jdk1.5及其以前默认68%,1.6以上默认92%。

小结

Serial GC:最小化地使用内存和并行开销。

Parallel GC:最大化应用程序的吞吐量。

CMS GC:最小化Gc的中断或停顿时间。

G1

jdk9默认使用,在延迟可控的情况下获得尽可能高的吞吐量。主要针对多核CPU以及大量内存的机器,兼顾年轻代和老年代的回收。将堆内存(Eden、survivor1、survivor2、老年代等)分割成很多不相关的区域(Region,物理上可不连续)。后台维护一个以回收时间和回收价值排序的优先列表,region作为基本回收单位,每次回收只需要回收部分Region区。

JVM使用Remembered Set解决一个对象被不同Region区引用问题,避免全局扫描,每个Region都有一个对应的Rememberd Set(用来判断对象是否存活),若写入的数据有引用其他Region区的数据时,会去更新被引用数据的Region的Rememberd Set。

Region分区图

image-20201205151643878

注:空白为未使用区,humongous主要是为了存储大对象若对象超过0.5个Region时,就放到humongous区。

工作流程

image-20201205151949513

年轻代并行GC

image-20201205153301399
1.扫描根。
根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口。

2.更新RSet.
处理dirty card queue中的card,更新RSet。 此阶段完成后,RSet可以准确的反映老年代对所在的内存分段中对象的引用。

注:对于应用程序的引用赋值语句bject.field=object,JVM会在之前和之后执行特殊的操作以在dirty card queue中入队-个保存了对象引用信息的card。在年轻代回收的时候,G1会对Dirty Card Queue中所有的card进行处理,以更新RSet,保证RSet实时准确的反映引用关系。这是为了性能的需要,RSet的处理需要线程同步,开销会很大,使用队列性能会好很多。

3.处理RSet。
识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。

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

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

并发标记过程

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

2.根区域扫描(Root Region Scanning) : 扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young GC之前完成。

3.并发标记(Concurrent Marking): 在整个堆中进行并发标记(和应用程序并发执行),此过程可能被youngGC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例,默认超过65%在回收阶段才会被回收)。

4.再次标记(Remark): 由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法: snapshot-at-the-beginning (SATB)。

5.独占清理(cleanup,STW): 计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫。是STW的。这个阶段并不会实际上去做垃圾的收集。

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

导致Full GC的原因

  • Evacuation的时候没有足够的to-space来存放晋升的对象。
  • 并发处理过程完成之前空间耗尽。

优势

空间整合

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

可预测的停顿时间模型(即:软实时soft real-time)

能明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿情况的发生也能得到较好的控制。

G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。保证了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:设置并发标记的线程数。

-XX: InitiatingHeapOccupancyPercent设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。

七种垃圾回收器对比

image-20201205153827039

日志分析

使用-XX:+PrintGCDetails 打印GC日志

-Xloggc: path/gc_log. log 将日志输出到文件

Minor GC

1607148456(1)

FullGC

1607148484(1)