1、为什么需要分代垃圾收集?
在Java 垃圾回收(基本过程)部分已经讲过,Java虚拟机将会逐一标记和压缩堆栈空间中的无引用对象。这样对所有对象的扫描、标记和压缩操作的代价,会随着对象数量的增加,而逐渐增大。此时用户程序中分配和使用的对象越多,垃圾回收所需的时间越长,从而会对用户程序的性能产生严重的影响。
通过对Java程序中对象的生存时间(从对象创建到对象回收的间隔)进行统计分析发现:发现大部分对象在很短的时间内就会失效,也即是说大部分对象的存活时间并不长。这种规律的发现,是Java垃圾分代回收的基础。一个简单的应用程序,内存统计示例如下图所示:

上图可见,存活时间长的对象随横轴越来越少,左侧的高峰表示大部分对象的生存周期很短。
2、JVM 分代
根据上面发现的规律,设计了堆内存的分代管理策略,从而能够显著提升JVM的效率。方法是将堆内存划分为几个区域,分别是:新生代、老年代和永生代。如下图所示:

Stop the World 事件:垃圾的回收过程属于一种叫 "Stop the World" 的事件。在这种事件发生时,所有的程序线程都要暂停,直到事件完成为止。
(1)年轻代(Young Gen):该内存区域主要用于存放新创建的对象,该区域的内存容量相对会较小,垃圾回收也比较频繁。年轻代又可进一步分成1个Eden Space和2个Suvivor Space(编号为0和1)。
a、当对象在堆中被创建时,将进入年轻代的Eden Space。如下图所示:

b、随着程序创建对象的增加,Eden空间会慢慢被填满,如下图所示:

c、当 Eden 空间填满时,会触发轻微的垃圾收集,此时JVM会扫描Eden Space和Suvivor Space 1。对于仍然存活的对象,则复制到Suvivor Space 0中,同时幸存对象的年龄增加,Eden区和S1被清空,如下图所示:

d、当 Eden 空间再次被填满时,会继续触发轻微的垃圾收集。此时Suvivor Space的0和1的角色互换。此时JVM会扫描Eden Space和Suvivor Space 0。对于仍然存活的对象,则复制到Suvivor Space 1中,同时幸存对象的年龄再次增加。注意,此时Survivor区中有了不同年龄的对象,Eden区和S0被清空,如下图所示:

e、当 Eden 空间再次被填满时,会继续触发轻微的垃圾收集,会继续重复同样的操作。这一次Survivor区仍然会交换。被引用的对象移动到S0,。幸存对象的年龄继续增加,Eden区和S1被清空。如下图所示:

f、 如果其中一个Suvivor Space已满,或者在多次扫描Suvivor Space,某个对象仍然存活(某个对象的年龄超过年龄阈值),将其移动到Old Gen。如下图所示:

(2)老年代(Tenured Gen):该内存区域主要存放JVM中生命周期较长的对象(经过几次的Young Gen的垃圾回收后仍然存在),该区域内存相对较大,垃圾回收也相对不频繁(譬如可能几个小时一次)。老年代主要采用压缩的方式来避免内存碎片(将存活对象移动到内存片的一边,也就是内存整理)。当然,有些垃圾回收器(譬如CMS垃圾回收器)出于效率的原因,可能会不进行压缩。老年代的垃圾回收也会触发STW(Stop the World)。通常,其回收速度会慢很多。所以,对于响应性的应用程序,应该尽量避免对老年代的垃圾回收。还要注意,老年代垃圾回收的STW的时长受年老代垃圾回收器类型的影响。
(3)永生代(Perm Gen):该内存区域主要存放类定义、字节码和常量等很少会变更的信息。该内存区域是由JVM在运行时根据应用程序使用的类来填充的。此外,Java SE类库和方法也存储在这里。如果JVM发现某些类不再需要,并且其他类可能需要空间,则这些类可能会被回收。
3、世代垃圾收集过程
现在你已经对JVM内存管理分代的原因和过程都有了一定了解,现在是时候看看这些空间是如何相互作用的。 下面的图片将介绍JVM中的对象分配和老化的整体过程。

网友评论