在确定了哪些对象需要进行垃圾回收之后,jvm就要开始垃圾回收的工作了。那么jvm是如何进行垃圾回收的呢?
根据程序实际运行的情况,jvm关于垃圾回收有2条假说(即2条经验法则):
1)弱分代假说:绝大多数对象的生命周期都很短,绝大多数的对象都是朝生夕灭的。
2)强分代假说:熬过越多次垃圾收集过程的对象越难以消亡。
这两条假说解释一下是说,绝大多数的对象在创建之后都会很快“消亡”,很快需要被回收;同时熬过越多次垃圾回收的对象越有可能熬过下一次回收。
根据这两条假说,jvm的开发者提出了分代回收理论。分代回收理论简单来说就是可以根据对象的年龄(年龄指的是熬过垃圾回收的次数),划分不同的区域,分区存储回收不同年龄的对象。这样做有什么好处呢?想象一下,如果一个区域里全都是新创建的对象,根据弱分代假说,这些对象里的绝大多数很快将消亡,需要被回收。这时候只要关注少数还存活的对象,将这些对象移动到其他区域,然后就可以将这个区域的对象全部回收了。这样的做法效率很高。
现在绝大多数的jvm都遵循了分代回收理论来设计。jvm的堆被分成至少两个区域,新生代(young)和 老年代(old)。新生代存放才创建的对象,老年代存放少量存活的对象。
但是这样的做法也带来一个问题,如果新生代里的对象被老年代的对象所引用了怎么办?那在进行可达性分析的时候,就不得不扫描老年代的全部对象来保证可达性分析的正确。这样带来的性能损耗还是很大的。
为了解决跨代引用带来的性能问题,根据实际运行的情况,总结出了第3条假说:
3)跨代引用假说:跨代引用相对于同代引用来说仅占极少数。
这条假说其实是可以理解的。想象一下,如果新生代的对象因为被老年代的对象引用了而不能回收,那么这个对象就会被转移到老年代。原来存在的跨代引用情况也就随着消失了。
根据这条假说,在垃圾回收时,就可以不必为了极少数的跨代引用而扫描整个老年代。只需要在新生代上建立一个全局的数据结构(即 Remembered Set )。这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。当发生 GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。
参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版) 周志明