写在前面的话:本文讨论的虚拟机内存分代,均指HotSpot虚拟机。
jvm的内存分代策略:
java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般而言就是:新生代,老年代,永久代。
那为什么要分代?
堆内存是虚拟机管理的内存中最大的一块,也是垃圾回收最频繁的一块区域,程序所有的实例对象都是存放在堆内存中的。给堆内存分代,是为了提高对象内存分配和垃圾回收的效率。假如堆内存没区域划分,那么所有的新创建的对象和生命周期很长的对象都将放在一起,随着程序的执行,堆内存需要频繁的进行垃圾回收,而每次垃圾回收,都要遍历所有的对象,这个性能是很低下的,会影响GC的效率。
将堆内存分为新生代,老年代和永久代,就能提高GC的效率,具体提高效率的原因是:
新创建的对象会在新生代中分配内存,经过多次回收后,依旧存活下来的对象会存储到老年代中,静态属性,类信息等存放在永久代中(和方法区存的内容差不多)。
- 新生代中的对象存活时间比较短,只需要在新生代内存区域中进行GC;
- 老年代中对象生命周期比较长,内存回收的频率相对较低,不需要频繁的进行GC;
- 永久代回收效果很差,因为都是静态属性,类信息等,一般不进行GC;
对于新生代和老年代的GC特点,还可以采用不同的GC算法对其进行垃圾回收,因此,堆内存分代,可以提高GC的性能。
新生代,老年代,永久代就是最大粒度的内存划分了吗?
新生代是否有垃圾回收?
既然提出了这个疑问,那说明答案应该就是否定的了。
java虚拟机将内存划分为新生代,老年代,永久代。永久代是HotSpot虚拟机特有的概念,其他虚拟机没有永久代的概念,而且局官方文档的备注,jdk1.7以后已经开始有意去去除“永久代”了,把原本放在永久代中的字符串常量池移出,放在方法区。
下面的图,是堆内存的大致划分:
备注:图中的年轻代和文中说的新生代是一个概念
jvm堆内存划分的新生代中,又划分了三个区域:如图所示,分别是:Eden,From,To三个区域。
三个区域的内存大小比例为:Eden:From:To=8:1:1
加个说明:不同的大拿写的书中或者译文中,对From,To这两个区域有不同的称呼,有的叫survivor0,survivor1,也有的叫S0,S1.
这三个区域大小划分,也是前辈先驱经过各种计算,算法推导出来的,过程我也不知道,结果就是,新生代中采用复制算法,进行垃圾回收,这个比例可以充分利用内存空间。
那复制算法具体是怎么样的流程呢或者怎么样触发的呢?
首先,新创建的对象都是在Eden区域分配(大对象不是在这个区域,而是直接放在了老年代),当Eden区域没有足够的内存空间进行分配时,虚拟机就会发起一次Minor GC。GC开始之前,对象只会存放在Eden区域和From区域(survivor0),To区域(survivor1区域)是空的。GC开始时,Eden中的所有存活对象都会被复制到To(survivor1区域),而From区域(survivor0区域)中,依旧存活的对象,会根据他们的年龄值决定他们的去向,年龄值达到阀值(默认是15,新生代中的对象,每经过一次GC,年龄值就会加1,并且将这个年龄值储存在对象的header中)的对象,将会被移动到老年代中,没有达到阀值的对象,也会被复制到To区域(survivor1区域),接着清空Eden区域和From(survivor0)区域。这个时候,所有经历这次GC依旧存活的对象都在To区域(survivor1)中,接着,From和To连个区域交换角色,From成为了To区域,To成为了From区域,这样,此时的To区域又是空的了。具体过程如下图所示:
第一步:GC开始前的状态
第二步:GC开始,将Eden中存活的对象复制到To区域,将From区域中存活且年龄值未到阀值的对象复制到To区域。将From区域中存活且年龄值达到阀值的对象移动到老年代。
第三步:清空Eden区域和From区域
第四步:将From区域与To区域的角色互换(这可能就是From与To区域大小1:1的原因)
经过以上四步,新生代中的一次采用复制算法的Minor GC(次收集),就这样完成了。
老年代是否有垃圾回收?
老年代中的对象生命周期较长,存活率较高,因此GC的频率相对较低,而且GC的速度也比较慢。
当老年代区域内存不足以分配时,就会触发Full GC,即全收集。
永久代是否有垃圾回收?
永久代存储类信息,常量,静态变量,即时编译器编译后的代码等数据,对这一区域而言,java虚拟机规范指出,可以不进行垃圾回收。
如有什么不对的地方,请指正。