1.前言
如果您的女盆友非常喜欢买买买,她将会面临一个严峻的问题——储物空间不够。
所以她需要定期**“断舍离”**:过一段时间将陈旧的包包、衣服移到不常用的储物柜,再过一段时间把这些不常用的衣物丢掉。
Java代码也像女盆友一样,喜欢**“买买买”**——创建大量对象,所以也会面临**内存空间不足**。
JVM的垃圾回收机制承担了**“断舍离”**的职责。
下面,我们来宏观地理解一下垃圾回收体系的主脉络。
2.Where:垃圾收集的目标区域
目标区域
垃圾回收,首先要圈定范围。
垃圾回收的目标区域主要发生在运行时数据区的堆、方法区:
稍微回顾一下运行时数据区的一些知识:
- 堆(Heap)
堆=年轻代+老年代
年轻代=伊甸园区(Eden)+幸存者区0(S0)+幸存者区1(S1)
- 方法区(Method Area)
方法区是站在类加载子系统的角度的一种逻辑说法。
方法区的具体实现是什么呢?
JDK8之前,是永久代。
JDK8以后,是采用堆外内存实现的元空间。
所以,更准确地说,垃圾回收的目标区域主要发生在运行时数据区的年轻代、老年代、元空间。
被回收区域的特征
根据分代理论,运行时数据区的各个区域存放的对象有如下特点:
- 伊甸园区存放的对象,可以简化理解为新对象,有点像刚买回来的2021新款LV包包
- 幸存者区存放的对象,可以简化理解为存活了一段时间的对象,有点像去年买回来的LV包包
- 老年代存放的对象,可以简化理解为存活很久的对象或者很大的对象,有点像过气的LV包包
不同区域,不同回收方式
每次收拾储物空间,可以根据当前的时间、当前的需要,分批收拾。
垃圾收集也一样,面对不同区域的对象,也会采用不同的回收方式分批回收:
- 部分收集法
- YGC:也叫Minor GC,针对年轻代垃圾收集。
- OGC:也叫Major GC,针对老年代垃圾收集。
- 混合GC:年轻代、老年代一起垃圾收集。
- 全量收集法:年轻代、老年代、元空间一起垃圾收集。
3.When:何时会触发垃圾回收
明确了垃圾回收的范围,下一步就是明确回收时机。
- YGC:年轻代的伊甸园区满,就好像放新款包包的储物格满了,就会触发YGC。
- OGC:老年代满,就好像老款专属储物格满了,会触发OGC。
- FullGC:触发FullGC的条件很多(详见下图),就好像整个储物柜一起收集。
4.How:垃圾回收算法
说明:本文篇幅有限,不展开任何算法的细节,仅帮助读者建立宏观概念,笔者将在后续文章中详细阐述各种算法细节。
4.1.什么是垃圾?
断舍离的时候,什么样的包包算**“过时”**?一定是有个标准的。
对于垃圾回收算法也一样,认定无用对象的经典算法有两种:
- 引用计数算法:此算法是在每个对象上增加一个计数器,表示这个对象被多少其它对象引用了。如果某个对象的引用计数器=0,则这个对象被定义为垃圾。
- 可达性分析算法:此算法规定了一组满足条件的对象作为根节点,从这些根节点出发,搜索对象的引用链条,最终形成一个有向图,图中孤立的对象节点,就被定义为垃圾。
上述垃圾评价算法,各有利弊,后续会展开讨论,在此仅需要有一个概念:这套算法,就是帮助JVM识别一个对象是否属于垃圾。
4.2.如何回收垃圾?
识别出了垃圾,就要有节奏地、高效地开展垃圾回收。
好吧,问题来了:
- 有节奏意味着慢慢来:肯定要给女盆友时间慢慢收拾,把稍微过时的包包放到不常用的储物格子,把非常过时的包包直接丢掉。
- 高效意味着快:如果女盆友收拾的太慢,就没有人给您做饭了。
这也是影响JVM性能优化最大的课题:
- 如何快速垃圾回收
- 如何避免STW问题:Stop The World问题,就是垃圾回收过程中,导致业务代码卡顿。
这些就是现存的这么多种垃圾回收算法要攻克的核心课题。
我们接下来宏观地理解一下各类垃圾回收算法:
我们先看看3种最基础、最经典的基础算法
- Mark-Sweep:标记-清除算法
- Copying:复制算法
- Mark-Compact:标记-压缩算法
从收集的区域看,有1种基于上述3种基础算法的组合算法:
- 分代收集算法:本质就是不同的区域,采用不同的上述3种基础算法。
从收集的方式看,有1种基于上述3种基础算法组合算法:
- 增量收集算法:本质是业务代码运行和垃圾回收交替进行。
随着Java的不断发展,又发展出一种新的门派
- 分区算法:本质是将内存区域划分为很多小方格,每个格子按需放对象,与分代理论最大的不同是,同一类型的对象不一定要放在逻辑连续的代。如下图,同样是伊甸园区的对象,并不需要占据逻辑连续的伊甸园区。
5.垃圾回收器:垃圾回收算法的具体实现
说明:本文篇幅有限,在后续文章中展开垃圾回收器的细节。
垃圾回收器是垃圾回收算法的具体实现,但是理解垃圾回收器对我们有两个难题:
- 有多种垃圾回收器
- 每种垃圾回收器都主攻某种场景,为了满足大部分业务场景,必须合理组合使用多种垃圾回收器
5.1.为什么会有多种垃圾回收器呢?
除了业务软件对JVM垃圾回收性能的诉求,还有商业原因(例如:Sun认了Oracle当爸爸,例如:RedHat企图当干爹,但亲爹Oracle不让)。
5.2.如何搭配使用多种垃圾回收器呢?
本文仅以JDK8为例,列举出垃圾回收器的经典搭配方法:
- Serial+Serial Old: Serial回收器处理年轻代,Serial Old回收器处理老年代。
- ParNew+CMS+Serial Old: ParNew处理年轻代,CMS处理老年代,当CMS失效时,Serial Old回收器兜底老年代。
- Parallel Scanvenge+Parallel Old: Parallel Scanvenge处理年轻代,Parallel Old处理老年代。
- **Parallel Scanvenge + Serial Old:**Parallel Scanvenge处理年轻代,Serial Old处理老年代。这种搭配关系在JDK14以后就开始废弃了。
- G1:G1是个神奇的存在,一个回收器通吃年轻代和老年代。这也是JVM目前主推的回收器。
总结
本文讲解了垃圾回收体系的主脉络:
- 垃圾回收发生在年轻代、老年代、方法区。
- 垃圾回收的方式:YGC(Minor GC)、OGC(Major GC)、FullGC
- 年轻代伊甸园区满时,触发YGC
- 老年代满时,触发OGC
- 有多种条件,触发FullGC(详见前文配图)
- 引用计数算法和可达性分析算法,用来识别哪些对象是垃圾。
- 垃圾回收的三种基础算法:标记-清除算法(Mark-Sweep)、复制算法(Copying)、标记-压缩算法(Mark-Compact)
- 垃圾回收的两种组合算法:分代收集算法、增量收集算法
- 垃圾回收的新门派组合算法:分区收集算法
- 由于商业原因和业务需求,JVM提供7种经典垃圾回收器。
- 实战时,搭配使用不通过垃圾回收器,满足回收年轻代和老年代的场景。
- G1是一种特殊的垃圾收集器,通吃年轻代和老年代的垃圾回收。
如果从宏观上理解了垃圾回收体系的全貌,相信聪明的读者不用死记硬背也能得到题目的答案,请您将答案写在下面的评论区吧!
废弃
如果把JVM类比为一座工厂,那么运行时产生的各种对象就像这座工厂里的打工人,运行时数据区就像打工人的工位。
铁打的营盘流水的兵,工位有限,企业主会安排HR开展裁员工作——清理没价值的打工人,为新人腾出工位。
垃圾回收体系就是JVM中,扮演了这个最无情的角色。
站在打工人(对象)的角度,垃圾回收体系是无情的存在。
但站在企业主(JVM)的角度,垃圾回收体系是维系正常生产的重要部件。