转载自:
java中的分代垃圾回收策略
什么是分代垃圾回收策略?
根据对象的生命周期的长短把对象分成不同的种类(年轻代,年老代和持久代)并分别进行内存回收,就是分代垃圾回收!值得注意的是,这种划分对象的手段并不是自动进行的,而是伴随着回收过滤进行的,也就是说年轻代与年老代之间的转换是伴随着对象回收的,只有经过了回收的洗礼后一些对象才会被选中成为年老代,而另外一些不幸的对象则早早地就被系统取走了小命。
为什么要运用分代垃圾回收策略?在java程序运行的过程中,会产生大量的对象,因每个对象所能承担的职责不同所具有的功能不同所以也有着不一样的生命周期,有的对象生命周期较长,比如Http请求中的Session对象,线程,Socket连接等;有的对象生命周期较短,比如String对象,由于其不变类的特性,有的在使用一次后即可回收。试想,在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,那么消耗的时间相对会很长,而且对于存活时间较长的对象进行的扫描工作等都是徒劳。因此就需要引入分治的思想,所谓分治的思想就是因地制宜,将对象进行代的划分,把不同生命周期的对象放在不同的代上使用不同的垃圾回收方式。
如何划分?将对象按其生命周期的不同划分成:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是类信息,所以与java对象的回收关系不大,与回收息息相关的是年轻代和年老代。
年轻代:年轻代又进一步可以划分为一个伊甸园(Eden)和两个存活区(Survivor space),伊甸园是进行内存分配的地方,是一块连续的空闲内存区域,在里面进行内存分配速度非常快,因为不需要进行可用内存块的查找。新对象是总是在伊甸园中生成,只有经受住了一定的考验后才能后顺利地进入到存活区中,这种考验是什么在后面会讲到。把存活区划分为两块,其实也是为了满足垃圾回收的需要,因为在年轻代中经历了“回收大劫”未必就能够进入到年老代中。系统总是把对象放在伊甸园和一个存活区(任意的一个),在垃圾回收时,根据其存活时间被复制到另一个存活区或者年老代中,则之前的存活区和伊甸园中剩下的都是需要被回收的对象,只要对这两个区域进行清除即可,两个存活区是交替使用,循环往复,在下一次垃圾回收时,之前被清除的存活区又用来放置存活下来的对象了。一般来说,年轻代区域较小,而且大部分对象是需要进行清除的。
年老代:在年轻代中经历了N次回收后仍然没有被清除的对象,就会被放到年老代中,可以说他们都是久经沙场而不亡的一代,都是生命周期较长的对象。对于年老代和永久代,就不能再采用像年轻代中那样搬移腾挪的回收算法,因为那些对于这些回收战场上的老兵来说是小儿科,而是采用一种称为“标记-清除-压缩(Mark-Sweep-Compact)”的算法。标记的过程是找出当前还存活的对象,并进行标记;清除则是遍历整个年老区,找到已标记的对象并进行清除;而压缩则是把存活的对象移动到整个内存区的一端,使得另一端是一块连续的空间,方便进行内存分配和复制。
持久代:用于存放静态文件,比如java类、方法等。持久代对垃圾回收没有显著的影响。
什么时候进行回收?由于对象进行了了分代处理,所以垃圾回收区域和时间也不一样。回收(Gabage Colection )的类型有两种:Scavenge GC和Full GC。
Scavenge GC:当新对象生成,但在Eden申请空间失败时就会触发Scavenge GC,对Enden去进行GC,清除掉非存活的对象,并且把存活的对象移动到Survivor区中的其中一个区中。前面的提到考验就是Scavenge GC,也就是说对象经过了Scavenge GC才能够进入到存活区中。这种形式的GC只会在年轻代中进行,因为大部分对象都是从Eden区开始的,同时Eden区不会分配得太大,所以对Eden区的GC会非常地频繁。
Full GC:对整个对进行整理,包括了年轻代、年老代和持久代。Full GC要对整个块进行回收,所以要比Scavenge GC慢得多,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对Full GC的调节,如下原因可能触发它的执行:
年老代(Tenured)被写满
持久代(Permanent)被写满
System.gc()被显式调用
自上一次GC之后Heap的各域分配策略动态变化