JVM 从入门到实战 | 03 简述垃圾回收算法

428 阅读5分钟

引言

之前我们学习了 JVM 基本介绍 以及 什么样的对象需要被 GC ,今天就来学习一下 JVM 在判断出一个对象需要被 GC 会采用何种方式进行 GC。在学习 JVM 如何进行垃圾回收方法时,发现所谓的 JVM 垃圾回收思想和现实生活的场景有很多相似的地方。所以本文用餐厅回收餐桌的方式类比 JVM 垃圾回收算法,应该能帮助 JVM 学习的理解和记忆。

经典垃圾回收算

标记-清除(Mark-Sweep)

研发园开了家新餐厅,餐厅老板在考虑如何回收餐盘时首先使用了最简单的方式,那就是服务员在顾客用餐的过程中,不定时的观察餐厅,针对用完餐的顾客记录他们的位置(当然一般的服务员的脑海中自行处理),统一回收他们的餐具和餐盘。但是这种回收方式会有一个明显的问题,那就是回收后的餐厅座位,很有可能是不连续的。如果后续有同行的顾客(比如小情侣... ...)想坐在一起,那很可能找不到连续的座位。

复制算法(Copying)

为了解决餐厅座位碎片化的问题,餐厅的老板提出了一个大胆的想法,这是一个很会思考的老板。把餐厅的用餐区域分成两部分 A 厅和 B 厅,当对 A 厅中的餐桌做回收时,将 A 厅中还未用完餐的顾客,"请"到 B 厅去用餐,并且让这些顾客在 B 厅中拼桌用餐(为了餐位连续)。这样所有 A 厅中的位置都空余出来了,并且 B 厅中的用餐区域和未用餐区域都是连续的!简直是强迫症晚期。看似完美的解决了回收后餐位碎片化的问题。但是依然带来了其他的一些问题。

缺点:

  • 餐厅的运营区域是一个整体,现在只能同时对外开放 A 厅,运营空间变小了;

  • 当有很多顾客需要从 A 厅转移到 B 厅时,效率太低;

  • 用餐体验很差,不被骂娘才怪。

优点:

  • 不容易产生碎片

标记-整理算法(Mark-Compact)

当实行复制算法解决餐位回收后不连续的问题,餐厅的老板针对新问题又有了新想法,只要移动顾客就可以解决碎片化问题,为啥我要将餐厅分成两个的部分呢?毕竟那样不能最大效率的利用餐厅的用餐区域。创造性的提出了标记-整理算法,结合前面两中方法的优缺点,当餐厅准备回收餐位时,移动所有未用晚餐的顾客,并且让从餐厅的第一桌开始拼桌。这样保证后面的餐桌都是已经回收了餐盘的并且回收后的座位都是连续的。这样既提高了餐厅餐桌的利用率又保证了当有大量组团顾客进店用餐时,餐厅能够提供大量的连续餐桌(这个例子不恰当,如果真的碰到一个要让人不断移动形成空位的餐厅,你怕是要开喷了)。

分代收集(Generational Collection)

如果还是用开餐厅的方式来思考 JVM 的话,可以把分代回收看做餐厅针对不同顾客的等级推出的个性化服务。分代收集算法并没有新的思想,只是根据对象存活周期的不同将内存划分为几块,一般把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,大量的对象都是“朝生夕死”,每次垃圾回收时,都可以发现大量对象死去,所以针对新生代的垃圾回收一般选择复制算法。只需要复制少量存活对象就可以完成收集。针对老年代的垃圾回收,对象的存活时间较长,就必须使用标记-清除或者标记-整理算法来进行回收。

在新生代中,绝大多数的对象都是“朝生夕死”的,新生代并不需要按照 1:1 的比例划分内存空间,而是将内存分为一个较大的 Eden 空间和一个较小的 Survivor 空间,并将 Survivor 空间分成两个较小空间,分别是 From Space 和 ToSpace。每次使用 Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。Hotspot 虚拟机默认 Eden 和 Survivor 的大小比例是 8:1,也就是每次新生代可用内存空间为整个新生代容量的 90%。

这里很显然会有一个问题,理论上每次新生代 GC 都会回收绝大多数的对象,但是无法保证 GC 存活后的对象大学都不超过整个新生代的 10%。当 Survivor 空间的内存不够用是,就需要老年代做内存担保(分配担保策略,前边两篇文章有提过,读者可自行查阅)。同样用餐厅的理论来理解,你希望把 A 厅的顾客转移到 B 厅,但是 B 厅已经没有足够空间容纳所有顾客了,这时候可以选择将顾客安置在 VIP 包厢【老年代】。并且每次在新生代 GC 中存活的对象,其年龄就会 +1,默认情况下年龄达到 15 的对象会被转移到老年代。

这里也可以简单理解为我们给餐厅的忠实吃货办了张 VIP 卡... ...

所以,同学,办卡吗?办卡享五折优惠哈!

... ...

参考文档

《深入理解Java虚拟机》周志明 著