熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验(一)

前言:作为一个实习生,当你的简历上出现这句话:“熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验”,是不是瞬间就会感觉自己的简历就上了一个档次,当然你也不能瞎写,简历上的东西,必须是货真价实的,并且面试官问到要回答自如。接下来的几篇文章我都会从这句话给大家普及下这方面的知识,我的这几篇博客都是对马士兵老师相关jvm视频进行一个整理。进行一个浅显易懂的解答,希望大家有所收获,看这篇文章需要对jvm有一个大致的了解,可以看看我之前写的一个总结jvm总结


我将从以下几个方面来介绍

  1. 常见的几种垃圾回收器
  2. 什么是STW(stop-the-world,关于STW是面试常考的)
  3. 什么是CMS(Concurrent Mark Sweep 并发标记清除 里程碑)
  4. 垃圾回收期的演化历程

借用马老师学的一句话:学知识先学脉络,再去抓细节。


一.常见的几种垃圾回收器

熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验(一)
大家从图中可以看到有将近10种垃圾回收器。并且左边的6种回收器可以两两之前互相组合使用,对应图中的虚线。

我们知道目前的垃圾回收都会采取分代回收,所以垃圾回收器也分为不同年龄带的收集器。

1.什么是Serial Young  (年轻代)

官方描述:a stop-the-world(STW),copying clollector which uses a single GC thread

关于STW我会在后文进行介绍,从描述中可以知道 Serial收集器采用copy算法并且是单线程回收

2.什么是 Serial Old (老年代)

官方描述:a stop-the world,mark sweep-compact collector that uses a single GC thread.

与Serial Young类似,不过工作在老年代,采用标记整理或者标记清除算法。

3.什么是 Parallel Scavenge (年轻代)

官方描述:a stop-the-world, copying collector which uses multiple sGC thread

与Serial Young不同点采用的多个gc线程回收

4.什么是 Parallel Old  (老年代)

与Serial Old不同点采用多个gc线程回收

ParNew和CMS会在后文进行说明

由此可以总结到:Serial开头的为单线程收集器 Parallel开头的为多线程收集器

ps:那目前jdk1.8采用的默认回收器是那种呢?

我们进入cmd输入:java -XX:+PrintCommandLineFlags -version

熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验(一)

第二排末尾:-XX:+UseParallelGC即默认使用的是 Parallel Scavenge + Parallel Old  (简称ps+po或者ParallelGC)

或者使用

java -XX:+PrintFlagsFinal

熟悉GC常用算法,熟悉常见垃圾收集器,具有实际JVM调优实战经验(一)

可以看到为两个都为ture


二.什么是STW

上文在介绍收集器的时候官方有段这样一段文字,不知道大家注意到没 stop-the-world ,这里我们可以将垃圾回收的这个过程,类比成 妈妈(gc线程)在收拾孩子房间(孩子房间的随意摆放的玩具就是垃圾对象)这样的一个过程。STW呢就是说妈妈在整理房间的时候呢,把孩子赶出房间,等妈妈整理完,孩子在进来。意思就是说垃圾回收线程工作的时候,其他工作线程全部停止这就叫STW,STW时间大了,妈妈回收时间过长了,很影响用户体验,用户一直得不到响应


三.什么是CMS

为了解决上述STW问题,CMS诞生了,它是一种承上启下的收集器,但它却是属于前浪被后浪拍死在沙滩上的那种。它存在几个严重缺点,后面我会说。CMS全称Concurrent Mark Sweep(并发标记清除),另外,CMS也是一款真正意义上的并发收集器,能够与用户线程同时进行。虽然,并发回收过程中也有几个阶段需要Stop the world,但是由于任务简单,所以停顿时间非常短。

它有4个阶段:

一、初始标记

(1)标记老年代中所有的GC Roots引用的对象

(2)标记老年代中被年轻代中活着的对象引用的对象。

由于需要对所有的root节点对象进行标记,为了防止标记过程中有对象状态发生改变,所以需要STW,停止用户线程,但是因为root节点对象比较少整个标记的过程耗时短。

二、并发标记

从初始化标记阶段找到的root节点开始进行查找,找到所有的存活对象,此时是非STW的,用户线程同时进行,因此会有一些对象的引用状态发生改变。

为什么对象状态会发生改变呢?我们从线程角度来看。它有两种改变:

1.假设从root节点存在a->b->c这样一个引用关系,而此时发现垃圾回收线程发现b还是被引用着,但是因为是非STW,假设用户线程将a.b=null,此时b就是个”浮动垃圾”,其实这种情况不严重等待下一轮回收就把它回收了。

2.第二种情况就比较严重了,想必大家也应该猜到了,假设b已经被标记了,马上就要被回收了,但此时用户线程又将b指向了一个新的引用,此时如果b被回收了,后果就比较严重了,这就需要第三步remark

三、重新标记

这个阶段是STW的,它会对上一轮标记失误的引用,进行一个重新标记。

四、并发清除

移除那些没有被标记的对象,并回收占用的内存空间。

它的缺点:

1.无法回收浮动垃圾

2.因为CMS是采用标记-清除”算法实现的收集器,使用“标记-清除”算法收集后,会产生大量碎片,对分配大对象可能会频繁触发FULL GC

3.cpu敏感:因为它在回收时,会占用一部分cpu资源,并且预留一部分资源给用户线程,这样会导致引用程序变慢,总吞吐量下降,最严重的当CMS运行期间预留的内存无法满足程序其他线程需要,就会出现“Concurrent Mode Failure”失败,这时候虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。

但是不可否认,它确实是垃圾回收器中的一个里程碑,开启了并发回收。

而ParNew这种收集器与Parallel Scavenge区别就是,ParNew可以与CMS配合来使用。

 

ps:为了避免篇幅过长,对G1进行一个简单的介绍:

G1(Garbage-First )收集器是一种server-style 回收器,主要面向多核,大内存的服务器。G1 在实现高吞吐的同时,也最大限度满足了GC 停顿时间可控的目标。在Oracle JDK7 update 4 及 以后的版本全面支持G1 回收器功能。G1收集器主要为有如下需求的程序设计:

  • 可以像CMS 收集器 能同时和应用线程 一起并发的执行;
  • 实现压缩空间时用更少的停顿时间;
  • 满足可预测的GC停顿时间需求;
  • 不要牺牲太多的吞吐性能;
  • 不需要占用更多的Java Heap;

未来 G1 计划要全面取代CMS的。G1相比CMS有更多的优势,G1是压缩型收集器,G1通过依赖regions分区,可以实现压缩更充分。这样消除大部分潜在的碎片问题。G1提供更精准的可预测的垃圾停顿时间,满足用户指定垃圾回收时间的需求。


四.垃圾回收期的演化历程

垃圾回收器的发展路线

1.随着内存越来越大演化。

2.从分代到不分代(从G1开始逻辑上分代,物理上不分代,而ZGC之后逻辑物理都不分代)。

Serial回收器 适用于内存几十M(已被淘汰)

Paraller回收器 适用于内存几个G,10几个G也在用。

ParNew回收期 与Paraller Scavenge唯一区别就是可以组合CMS

CMS回收器  适用于几十个G  承上启下 开启了并发回收

G1回收器 号称上百G 逻辑分代,物理上不分代

ZGC,shenandoah回收器 上T  逻辑和物理都不分代

越到后面jvm参数越来越少,到后面或许就不需要我们进行调优了,。


第一篇总结就到此结束,后续我将继续更新