深入浅出JVM垃圾回收算法

在学习JVM如何进行垃圾回收方法时,发现所谓的JVM垃圾回收思想和现实生活的场景有很多相似的地方。所以用餐厅回收餐桌的方式类比JVM垃圾回收算法,应该能帮助JVM学习的理解和记忆。

0x01 经典垃圾回收算法

标记-清除(Mark-Sweep)

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

复制算法(Copying)

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

缺点:

  • 餐厅的运营区域是一个整体,现在只能同时对外开放A厅,运营空间变小了。
  • 当有很多顾客需要从A厅转移到B厅时,效率太低。
  • 用餐体验很差

优点:

  • 不容易产生碎片

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

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

0x02 分代收集(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虚拟机》周志明 著

Java基础:JVM垃圾回收算法

转载请标明出处病已blog https://ivonhoe.github.io/