Java中的分代垃圾回收策略
一、分代GC的理论基础
分代的垃圾回收策略,是基于这样一个事实:不同的对象的生命周期是不一样的。因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率。
在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比如Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。但是还有一些对象,主要是程序运行过程中生成的临时变量,这些对象生命周期会比较短,比如:String对象,由于其不变类的特性,系统会产生大量的这些对象,有些对象甚至只用一次即可回收。
试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间相对会长,同时,因为每次回收都需要遍历所有存活对象,但实际上,对于生命周期长的对象而言,这种遍历是没有效果的,因为可能进行了很多次遍历,但是他们依旧存在。因此,分代垃圾回收采用分治的思想,进行代的划分,把不同生命周期的对象放在不同代上,不同代上采用最适合它的垃圾回收方式进行回收。
二、代际划分
堆内存分为年轻代(Young Generation)和年老代(Old Generation)。年轻代又分为两种,一种是Eden区域,另外一种是两个大小对等的Survivor区域。
持久代即非堆内存,主要用于存储一些类的元数据,常量池,java类,静态文件等信息。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=<N>进行设置。
三、年轻代的垃圾回收
当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区,然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden区能尽快空闲出来。
首先,新对象的内存分配都是先在Eden区域中进行的,当Eden区域的空间不足于分配新对象时,就会触发年轻代上的垃圾回收,我们称之为"minor garbage collection".同时,每个对象都有一个“年龄”,这个年龄实际上指的就是该对象经历过的minor gc的次数。如图1所示,当对象刚分配到Eden区域时,对象的年龄为“0”,当minor gc被触发后,所有存活的对象(仍然可达对象)会被拷贝到其中一个Survivor区域,同时年龄增长为“1”。并清除整个Eden内存区域中的非可达对象。
当第二次minor gc被触发时(如图2所示),JVM会通过Mark算法找出所有在Eden内存区域和Survivor1内存区域存活的对象,并将他们拷贝到新的Survivor2内存区域(这也就是为什么需要两个大小一样的Survivor区域的原因),同时对象的年龄加1. 最后,清除所有在Eden内存区域和Survivor1内存区域的非可达对象。
当对象的年龄足够大(这个年龄可以通过JVM参数进行指定,这里假定是2),当minor gc再次发生时,它会从Survivor内存区域中升级到年老代中,如图3所示。
四、年老代的垃圾回收
当minor gc发生时,又有对象从Survivor区域升级到Tenured区域,但是Tenured区域已经没有空间容纳新的对象了,那么这个时候就会触发年老代上的垃圾回收,我们称之为"major garbage collection"。而在年老代上选择的垃圾回收算法则取决于JVM上采用的是什么垃圾回收器,通过的垃圾回收器有两种:Parallel Scavenge(PS) 和Concurrent Mark Sweep(CMS)。
参考资料
http://www.jianshu.com/p/778dd3848196
http://itindex.net/detail/45308-jvm-%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6-%E7%AD%96%E7%95%A5