Java 分代垃圾回收

JVM内存模型简述

一般会认为JVM的内存模型包括永久区内存(?Permanent?space?)和堆内存(heap?space。当然也有人会提到栈内存(stack?space,栈内存一般都不归在JVM内存模型中,因为栈内存属于线程级别。每个线程都有个独立的栈内存空间。 ?Permanent?space里存放加载的Class类级对象如class本身methodfield等等。heap?space是存运行数据的地方。可以想象Permanent?space占用的空间一般不会太大,虽然垃圾回收也是要做。此处要注重描述的分代垃圾回收指的是对于堆内存的处理。

分代的原因

因为不同的对象生命期不同,对于不同的对象采用不同的机制。在实现上就是把新生成的对象放在一起,其中该回收的回收掉,不能回收的被认为生命期比较长放到另外一个区域。对于不同的区域采用不同的回收手段。总体思路其实很简单,我们生活中整理东西一般也无外乎这样的思路,把常用的东西放在一起,发现哪些东西不常用了,就放到另外一个地方去。比如不太勤快的douma,整理家里的衣柜的方式。

一些对象在创建后很快就成为垃圾,另一些则在程序的整个运行期间一直保持生存。在程序运行中产生的大量对象大量的是产生后很快就不再用的对象,最简单如我们动不动new一个String,用完一般就被回收掉了。但有些是在运行过程中生命期比较长的,如HttpSession,线程,Socket等。

如果这些对象混在一起,每次垃圾回收都是尝试扫描整个堆空间,考察活的对象,会发现上面提到的哪些生命期比较长的对象在每次扫描的时候都是还在用,不可回收,对其进行频繁的扫描只是浪费时间和资源。因此要对其在回收策略上分别对待。

分代原理

分代收集器将堆分为多个代。在年轻的代中创建对象,满足某些提升标准的对象,如经历了特定次数垃圾收集的对象,将被提升到下一更老的代。分代收集器对不同的代可以自由使用不同的收集策略,对各代分别进行垃圾收集。

jvm-memory-generation

 

基于这样的考虑,在JVM的内存空间中把堆空间Heap?space分为Old?GenerationNew?Generation。其中Old?Generation中存放生命周期长久的实例对象,而新的对象实例一般放在New?Generation

New?Generation还可以再分为Eden(圣经中的伊甸园)、和两个Survivor区,新的对象实例总是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,可以向Old区转移存活的对象实例。

分代 作用
New?Generation Eden 创建新对象
Survivor Eden区和Old区的缓冲,两个Survivor区,用来实施复制算法。
Old?Generation Tenured?区 存放生命周期较长的对象

新生对象一般都是首先存放在年轻代上,准确说其实是先尝试创建在年轻代的Eden区上(如果Eden区能容纳下该新对象,在后续例子中有详细介绍)。

Eden区满时,仍然存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor区也满了的时候,从第一个Survivor区复制过来的此时还存活的对象,将被复制“年老区(Tenured)”。?Survivor两个区是对称的,没先后关系,就是作临时的缓冲用。从前面的过程能理解,同一个Survivor区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。可以看到,年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。

一般都采用复制算法Copying来对年轻代进行回收。关于这种算法的介绍请参考gc策略。简单讲就是指访问活跃对象了,忽略死的对象。把活的对象复制到另外一个区域。因为年轻代中大部分对象都是生下使用完即消亡的,需要复制的只是少量,这样复制的成本不算高。

年老代存放生命期期较长的对象。?在年轻代中经历了多次垃圾回收后仍然存活的对象,就会被放到年老代中。

年老代使用标记整理Mark-Compact算法。不同于年轻代,能添加到年老代的对象都是生命期比较长,都是些老不死的对象,即一般情况下,每次都年老代进行回收时,大部分对象都是存活的。因此不可能大量的复制(浪费空间又浪费效率),因此一般采用标记清理的方式是。详细参照GC策略介绍。

分代对象后不同的?GC

由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge?GC和Full?GC。

Scavenge?GC

一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge?GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

?Full?GC

对整个堆进行整理,包括YoungTenuredPermFull?GC因为需要对整个对进行回收,所以比Scavenge?GC要慢,因此应该尽可能减少Full?GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。

一般当年老代被写满,持久代被写满或者显式的调用System.gc()都会引起Full?Gc

参考:

原创文章。为了维护文章的版本一致、最新、可追溯,转载请注明: 转载自idouba

本文链接地址: Java 分代垃圾回收


,

No comments yet.

发表评论