1.前言

如果您的女盆友非常喜欢买买买,她将会面临一个严峻的问题——储物空间不够

所以她需要定期**“断舍离”**:过一段时间将陈旧的包包、衣服移到不常用的储物柜,再过一段时间把这些不常用的衣物丢掉。

Java代码也像女盆友一样,喜欢**“买买买”**——创建大量对象,所以也会面临**内存空间不足**。

JVM的垃圾回收机制承担了**“断舍离”**的职责。

image-20210122102351258

下面,我们来宏观地理解一下垃圾回收体系的主脉络。

2.Where:垃圾收集的目标区域

目标区域

垃圾回收,首先要圈定范围

垃圾回收的目标区域主要发生在运行时数据区的堆、方法区

image-20210107141117798

稍微回顾一下运行时数据区的一些知识:

  • 堆(Heap)

堆=年轻代+老年代

年轻代=伊甸园区(Eden)+幸存者区0(S0)+幸存者区1(S1)

image-20210107143110413

  • 方法区(Method Area)

方法区是站在类加载子系统的角度的一种逻辑说法。

方法区的具体实现是什么呢?

JDK8之前,是永久代。

JDK8以后,是采用堆外内存实现的元空间。

image-20210107143140440

所以,更准确地说,垃圾回收的目标区域主要发生在运行时数据区的年轻代、老年代、元空间

被回收区域的特征

根据分代理论,运行时数据区的各个区域存放的对象有如下特点:

  • 伊甸园区存放的对象,可以简化理解为新对象,有点像刚买回来的2021新款LV包包
  • 幸存者区存放的对象,可以简化理解为存活了一段时间的对象,有点像去年买回来的LV包包
  • 老年代存放的对象,可以简化理解为存活很久的对象或者很大的对象,有点像过气的LV包包

image-20210122135157426

不同区域,不同回收方式

每次收拾储物空间,可以根据当前的时间、当前的需要,分批收拾。

垃圾收集也一样,面对不同区域的对象,也会采用不同的回收方式分批回收:

  • 部分收集法
    • YGC:也叫Minor GC,针对年轻代垃圾收集。
    • OGC:也叫Major GC,针对老年代垃圾收集。
    • 混合GC:年轻代、老年代一起垃圾收集。
  • 全量收集法:年轻代、老年代、元空间一起垃圾收集。

image-20210107152239170

3.When:何时会触发垃圾回收

明确了垃圾回收的范围,下一步就是明确回收时机。

  • YGC:年轻代的伊甸园区满,就好像放新款包包的储物格满了,就会触发YGC。
  • OGC:老年代满,就好像老款专属储物格满了,会触发OGC。
  • FullGC:触发FullGC的条件很多(详见下图),就好像整个储物柜一起收集。

image-20210107154209483

4.How:垃圾回收算法

说明:本文篇幅有限,不展开任何算法的细节,仅帮助读者建立宏观概念,笔者将在后续文章中详细阐述各种算法细节。

4.1.什么是垃圾?

断舍离的时候,什么样的包包算**“过时”**?一定是有个标准的。

对于垃圾回收算法也一样,认定无用对象的经典算法有两种:

  • 引用计数算法:此算法是在每个对象上增加一个计数器,表示这个对象被多少其它对象引用了。如果某个对象的引用计数器=0,则这个对象被定义为垃圾。
  • 可达性分析算法:此算法规定了一组满足条件的对象作为根节点,从这些根节点出发,搜索对象的引用链条,最终形成一个有向图,图中孤立的对象节点,就被定义为垃圾。

上述垃圾评价算法,各有利弊,后续会展开讨论,在此仅需要有一个概念:这套算法,就是帮助JVM识别一个对象是否属于垃圾。

image-20210107172657461

4.2.如何回收垃圾?

识别出了垃圾,就要有节奏地高效地开展垃圾回收。

好吧,问题来了:

  • 有节奏意味着慢慢来:肯定要给女盆友时间慢慢收拾,把稍微过时的包包放到不常用的储物格子,把非常过时的包包直接丢掉。
  • 高效意味着快:如果女盆友收拾的太慢,就没有人给您做饭了。

这也是影响JVM性能优化最大的课题:

  • 如何快速垃圾回收
  • 如何避免STW问题:Stop The World问题,就是垃圾回收过程中,导致业务代码卡顿。

这些就是现存的这么多种垃圾回收算法要攻克的核心课题。

我们接下来宏观地理解一下各类垃圾回收算法

image-20210107170509963

我们先看看3种最基础、最经典的基础算法

  • Mark-Sweep:标记-清除算法
  • Copying:复制算法
  • Mark-Compact:标记-压缩算法

从收集的区域看,有1种基于上述3种基础算法的组合算法

  • 分代收集算法:本质就是不同的区域,采用不同的上述3种基础算法。

从收集的方式看,有1种基于上述3种基础算法组合算法

  • 增量收集算法:本质是业务代码运行和垃圾回收交替进行。

随着Java的不断发展,又发展出一种新的门派

  • 分区算法:本质是将内存区域划分为很多小方格,每个格子按需放对象,与分代理论最大的不同是,同一类型的对象不一定要放在逻辑连续的代。如下图,同样是伊甸园区的对象,并不需要占据逻辑连续的伊甸园区。

image-20210107161323691

5.垃圾回收器:垃圾回收算法的具体实现

说明:本文篇幅有限,在后续文章中展开垃圾回收器的细节。

垃圾回收器是垃圾回收算法的具体实现,但是理解垃圾回收器对我们有两个难题:

  • 有多种垃圾回收器
  • 每种垃圾回收器都主攻某种场景,为了满足大部分业务场景,必须合理组合使用多种垃圾回收器

5.1.为什么会有多种垃圾回收器呢?

除了业务软件对JVM垃圾回收性能的诉求,还有商业原因(例如:Sun认了Oracle当爸爸,例如:RedHat企图当干爹,但亲爹Oracle不让)。

5.2.如何搭配使用多种垃圾回收器呢?

本文仅以JDK8为例,列举出垃圾回收器的经典搭配方法:

  • Serial+Serial Old: Serial回收器处理年轻代,Serial Old回收器处理老年代。

image-20210107182038415

  • ParNew+CMS+Serial Old: ParNew处理年轻代,CMS处理老年代,当CMS失效时,Serial Old回收器兜底老年代。

image-20210107182113952

  • Parallel Scanvenge+Parallel Old: Parallel Scanvenge处理年轻代,Parallel Old处理老年代。

image-20210107182137752

  • **Parallel Scanvenge + Serial Old:**Parallel Scanvenge处理年轻代,Serial Old处理老年代。这种搭配关系在JDK14以后就开始废弃了。

image-20210107182201742

  • G1:G1是个神奇的存在,一个回收器通吃年轻代和老年代。这也是JVM目前主推的回收器。

image-20210107221548740

总结

本文讲解了垃圾回收体系的主脉络:

  • 垃圾回收发生在年轻代、老年代、方法区。
  • 垃圾回收的方式:YGC(Minor GC)、OGC(Major GC)、FullGC
  • 年轻代伊甸园区满时,触发YGC
  • 老年代满时,触发OGC
  • 有多种条件,触发FullGC(详见前文配图)
  • 引用计数算法和可达性分析算法,用来识别哪些对象是垃圾。
  • 垃圾回收的三种基础算法:标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-压缩算法(Mark-Compact)
  • 垃圾回收的两种组合算法:分代收集算法、增量收集算法
  • 垃圾回收的新门派组合算法:分区收集算法
  • 由于商业原因和业务需求,JVM提供7种经典垃圾回收器。
  • 实战时,搭配使用不通过垃圾回收器,满足回收年轻代和老年代的场景。
  • G1是一种特殊的垃圾收集器,通吃年轻代和老年代的垃圾回收。

如果从宏观上理解了垃圾回收体系的全貌,相信聪明的读者不用死记硬背也能得到题目的答案,请您将答案写在下面的评论区吧!

image-20210122140538305

废弃

如果把JVM类比为一座工厂,那么运行时产生的各种对象就像这座工厂里的打工人运行时数据区就像打工人的工位

铁打的营盘流水的兵,工位有限,企业主会安排HR开展裁员工作——清理没价值的打工人,为新人腾出工位。

垃圾回收体系就是JVM中,扮演了这个最无情的角色。

image-20210107150610240

站在打工人(对象)的角度,垃圾回收体系是无情的存在。

但站在企业主(JVM)的角度,垃圾回收体系是维系正常生产的重要部件。