先来看一张图,串行代表两个垃圾回收器按顺序执行,并行代表同时执行。STW代表工作线程暂停,Stop The World的意思。

常用命令:

-XX:+PrintCommandLineFlags,查看使用的垃圾收集器

-XX:+UseSerialGC,指定使用Serial GC处理新生代,并且默认会使用Serial Old GC处理老年代

-XX:+UseParNewGC,指定新生代使用ParNew GC,-XX:+UseConcMarkSweepGC,指定老年代使用CMS GC

-XX:+UseParallelGC,指定新生代使用Parallel GC,-XX:+UserParallelOldGC,指定老年代使用Parallel Old GC,这两个配置一个,另一个自动激活

1. Serial GC、Serial Old GC

Serial GC和Serial Old GC是比较早的垃圾回收器,那个时候一般的服务器CPU还只有一核,所以一个线程进行垃圾回收就够了。串行的意思就是两个垃圾回收器不能同时执行。

2. Parallel GC、Parallel Old GC

Parallel GC进行垃圾回收时也会暂停用户线程(STW),与Serial GC的不同就是它时多线程进行垃圾回收的。下面画个图方便理解垃圾回收过程。
《JVM第9课》垃圾回收器-LMLPHP

在一次垃圾回收过程中,会进行一次STW,并且会有多个线程同时进行垃圾回收,回收完后用户线程回复运行。

3. CMS GC

CMS全称是ConcMarkSweep,即并发标记清除。但是它不是某个JDK默认的,要使用的话就加上-XX:+UseConcMarkSweepGC参数。

CMS GC是Java9之前我们很常用的垃圾回收器,是针对老年代的使用标记-清除算法的垃圾回收器。我们一般会使用CMS GC替代默认的老年代垃圾回收器,因为CMS GC的特点是低暂停。实现低暂停的方法是在初始标记阶段只标记GC Roots直接可达的对象,这个阶段比较短,所以对用户线程来说是低暂停。

下面用一张图展示它的垃圾回收过程:

《JVM第9课》垃圾回收器-LMLPHP

阶段一,初始标记:

  • STW,暂停所有用户线程。
  • 然后标记GC Roots直接可达的对象,就是第一层对象。
  • 一旦标记完就恢复用户线程继续执行。
  • 这个阶段很快。

阶段二,并发标记:

  • 从上一个阶段标记出的对象,开始遍历整个老年代,标记出所有的可达对象。
  • 耗时虽然比较长,但好在不需要STW,可与用户线程一起执行。
  • 采用的是三色标记算法(大家自行拓展)。

阶段三,重新标记:

  • 由于在并发标记阶段,应用程序线程也在运行,可能会导致一些对象的状态发生变化,例如新的对象被创建或旧的对象被删除。
  • 为了修正这些问题,需要进行一次短暂的停顿,重新检查并更新标记状态
  • 这个阶段也是“Stop-The-World”的,但也不会太长。

阶段四,并发清理:

  • 清除垃圾对象,释放空间供后续分配使用。
  • 清理工作与应用程序线程并发进行,不会引起停顿。

阶段五,并发重置:

  • CMS收集器会准备下一次垃圾回收循环,重置内部数据结构等。
  • 这一阶段也是与应用程序并发进行的。

CMS整个垃圾回收过程更长了,但是STW的时间变短了,而且在垃圾回收过程中大部分时间用户线程可以执行,所以用户体验更好了。

在并发清理过程中,可能用户线程会产生新的垃圾,这些就是“浮动垃圾”,只能等到下一次GC时来清理。

如果在并发标记、并发清理过程中,用户线程产生的新对象要进入老年代,但是老年代空间又不够,那么就会导致“concurrent mode failure”,此时就会利用Serial Old来做一次垃圾回收,就会做一次全局STW。

由于采用的是标记-清除算法,所以会产生内存碎片,可以通过参数 -XX:+UseCMSCompactAtFullCollection 让JVM在执行完标记-清除后再做一次整理,也可以通过 -XX:CMSFullGCsBeforeCompaction 来指定多少次GC后来做整理,默认是0,表示每次GC后都整理。

CMS垃圾回收器能够在大多数时间内与应用程序并发执行,减少了因垃圾回收引起的停顿时间。然而,CMS也有一些缺点,比如它可能产生大量的浮动垃圾(floating garbage),并且在某些情况下仍然会导致较长的停顿。随着Java版本的发展,G1 GC和ZGC等更先进的垃圾回收器逐渐取代了CMS的地位。

4. G1(Garbage-First)

G1垃圾回收器是在 Java 7 update4 之后引入,并在Java 9中成为默认的垃圾收集器的。G1的设计目标是提供高吞吐量的同时,实现可预测的暂停时间,特别适合处理大堆内存的应用场景。下面介绍一些G1垃圾回收器的内存结构。

先看张图:

《JVM第9课》垃圾回收器-LMLPHP

之前我们讲的垃圾回收器把堆内存分为Eden、S0、S1、老年代这四个区域,每个区域都是连续的。而在G1中,整块堆内存被分成2048个大小相等的region,这些region在物理上不要求是连续的,但在逻辑上是连续的。每个region可以独立地作为Eden、Survivor或老年代的一部分。所以说G1还是把内存分为了Eden、Survivor、老年代区,只不过空间可以是不连续的了。

Humongous区是专门用来存放大对象的(如果一个对象大小超过了一个region的50%,那么就是大对象)

用一张图展示垃圾回收过程:

《JVM第9课》垃圾回收器-LMLPHP

阶段一,初始标记:

  • 短暂的STW。
  • 标记出直接可达对象。

阶段二,并发标记:

  • 并发遍历整个堆,标记所有可达对象。
  • 不需要STW。
  • 三色标记。

阶段三,最终标记:

  • 这也是一个较短的STW。
  • 修正并发标记期间由于应用程序运行而可能产生的误差。

阶段四,筛选回收:

  • 需要STW,来清除垃圾对象。
  • 可通过 -XX:MaxGCPauseMillis 来指定GC的STW停顿的时间,所以可能并不会回收掉所有的垃圾对象,默认为200ms(略小于人的平均反应速度)。
  • 由于回收时间有限,所有会选择更有回收价值(垃圾更多)的区域进行回收。这也是Garbage-First(垃圾优先)的名字由来。
  • 采用的是复制算法,不会产生碎片(会把某个region里的可达对象移动到空白的region里)。

由于筛选回收阶段的STW时间可能不够用导致垃圾约积越多,为了避免OOM,G1 中还提供了另外三种垃圾回收模式:Young GC、Mixed GC 和 Full GC,它们有各自的触发条件。

Young GC: Eden区满,就会触发G1的YoungGC,对Eden区进行垃圾回收。

Mixed GC: 老年代的占用率达到了 -XX:InitiatingHeapOccupancyPercent 指定的百分比,回收所有的新生代以及部分老年代,以及大对象区。

Full GC: 在进行Mixed GC的过程中,采用的是复制算法,如果没有空的region可用,就会触发Full GC,会STW,并采用单线程来进行标记-整理算法进行GC,相当于一次Serial GC。

11-12 19:54