1.2 JVM对象分代内存划分与垃圾回收

2016-02-20 16:45:09 3,808 0

前面的内容分析了JVM运行时的内存区域划分,并代码进行了实际的讲解。下面我们对象分代年龄的角度对JVM内存进行划分,这与JVM的垃圾回收息息相关。

  我们知道,JVM会动态的帮我们进行内存分配,对象没有引用的时候称为垃圾,垃圾回收机就会进行回收。

 有些对象会频繁的创建于销毁,例如我们在方法里面使用到局部变量,每次调用,方法里的局部变量就要经历创建与销毁的过程。

 而另外一些对象可能会常驻内存,例如静态成员变量,又或者我们在J2EE开发中的Servelt、Filter等。

来自IBM的一组统计数据:

98%的java对象,在创建之后不久就变成了非活动对象;只有2%的对象,会在长时间一直处于活动状态。

如果能对这两种对象区分对象,那么会提高GC的效率。在sun jdk gc中(具体的说,是在jdk1.4之后的版本),提出了不同生命周期的GC策略。

 现在我们从另外一个角度对JVM的运行时内存进行划分:堆(Heap)堆和非堆(Non-heap)内存

 按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。

简单来说堆就是Java代码可及的内存,是留给开发人员使用的;

非堆就是JVM留给 自己用的,所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法 的代码都在非堆内存中。

一、基于对象分代年龄的内存划分

从对象分代的角度来说,JVM的内存划分大致分为3块:分别是Permanent Generation(简称PermGen、持久代)、New Generation(又叫Young Generation,年轻代)和Tenured Generation(又叫Old Generation,年老代)。其中Permanent Generation位于non-heap区。New和Old是Java应用的Heap区,用来存放类的实例Instance的。我们在这里主要讨论的Heap区如何根据对象分代年龄进行划分。如下图所示:

QQ截图20160220161316.png

其中New Generation的目标就是尽可能快速的收集掉那些生命周期短的对象;New Generation又分为Eden SpaceFrom Survivor Space和To Survivor Space三块,Eden Space用于存放新创建的对象,From区和To区都是救助空间Survivor Space(图中的S0和S1);当Eden区满时,JVM执行垃圾回收GC(Garbage Collection),垃圾收集器暂停应用程序,并会将Eden Space还存活的对象复制到当前的From救助空间,一旦当前的From救助空间充满,此区的存活对象将被复制到另外一个To区,当To区也满了的时候,从From区复制过来并且依然存活的对象复制到Old区,从而From和To救助空间互换角色,维持活动的对象将在救助空间不断复制,直到最终转入Old域。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而且,Survivor区总有一个是空的。同时,根据程序需要,Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间,减少被放到年老代的可能。每一次垃圾回收后,Eden Space都会被清空。

Old区用于存放长寿的对象,在New区中经历了N次垃圾回收后仍然存活的对象,就会被放到Old区中;如那些与业务信息相关的对象,包括Http请求中的Session对象、线程、Socket连接,这类对象跟业务直接挂钩,因此生命周期比较长。

二、基于对象分代年龄的垃圾回收

在以上提到的任何一个空间不够用时,都会促使JVM执行垃圾回收。基于回收类型的不同,我们将垃圾回收划分为:

Minor GC(又叫Young GC):回收New Generation内存空间(Eden、From Survivor、To Survivor);

Full GC:回收New Generation和Tenured Generation、PermGen内存空间

Minor GC/Young GC触发

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

Full GC触发

Full GC要对整个Heap区进行回收,包括New、Old和PermGen,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM性能调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

·Tenured区被写满

·PermGen区被写满

·System.gc()被显示调用

·上一次GC之后Heap的各域分配策略动态变化

需要注意的是,Full GC与YoungGC是无法完全区分开来的,很多情况下,Full GC是由YoungGC导致的。例如在Eden Space中的对象经历过几次垃圾回收,依然还存活,就会移动到Tenured区,而如果此时Tenured区空间不够,就会出发垃圾回收,过程如下图所示:

minor-gc-major-gc-full-gc.jpg

对于Survivor区域,其实际上就是经历过几次垃圾回收依然没有被回收掉的对象(称之为幸存者)过渡到Tenured区的临时存储空间。