要说理解JVM的垃圾回收,什么引用计数,Copy GC,mark & compaction好像都不是必须要掌握的东西。真要说对普通的Java程序员比较重要的东西,我觉得必须得有分代式垃圾回收。
我们在遇到Java进程比较卡的时候,往往第一个想到的就是使用 jstat 的 GC 相关选项查看一下进程的GC状态。
如上图所示,我们使用gcutil来观察Java堆使用的情况。gcutil打印出的数字分别代表了某一个区域的内存使用的百分比。
好了,我们一再地遇到年轻代和老年代的叫法,它们到底是什么东西呢?
一个Java对象,存活的时间不同,则它适用的GC算法就会有所不同。比如说系统中的大多数对象的存活时间都很短,那么Copy GC就会很高效,因为每次GC,要保存的存活对象会很少。然后大多数内容都会在一个巨大的空间里集中释放掉。但如果大多数对象的存活时间很长,那么Copy GC就不再高效了,因为每次要搬运太多的内容。这个时候不移动对象的引用计数法或者mark sweep法就会很高效了。
基于这样的观察,人们提出了一种分代式的垃圾回收策略。
把空间划分成年轻代和老年代两块。创建新的对象总是先在年轻代里创建,当年轻代空间不足了,就会触发年轻代的GC,我们称之为young GC,经过一次YGC,对象的年龄就加1,当对象的年龄达到一个阈值时,这个对象就可以进入老年代了。
可以看到,年轻代总是会比老年代先满,年轻代的回收也更频繁。如果能撑过多次YGC的,这种对象的生命周期往往会非常长(例如某些全局单例Config之类的),那么把它们放到老年代,避免在YGC时被拷来拷去是能够有效地提升GC效率的。
一个对象从年轻代进入老年代,叫做晋升(promotion)。晋升这个词我们后面会一再地重复它,它是Java分代GC最容易引发问题的地方。
分代式GC与前边所讲的Copy GC,Tracing GC是不同的,它不是一种单独的GC算法。年轻代或者老年代要使用前边所讲的GC算法的某一种,这样的话,分代式GC就相当于是两种不同的GC算法的组合了。
后面的文章我会逐个介绍年轻代和老年代所使用的不同的GC算法。