JVM 堆内存 分配与回收策略(对象的分配与回收)
1、堆划分
在JDK1.7以及之前的版本中,堆内存通常被分为三块区域:新生代、老年代、永久代。
新生代又分为:Eden区、From Survivor区(S0)、To Survivor区(S1)。默认8:1:1。
而在JDK1.8中情况发生了变化,把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)中。
JDK1.8也提供了一个新的设置Matespace(元空间)内存大小的参数,通过这个参数可以设置Matespace内存大小,这样我们可以根据自己项目的实际情况,避免过度浪费本地内存,达到有效利用。
JDK1.8堆划分
2、内存分配与回收策略
2.1 对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够的空间进行分配时,虚拟机发起一次 Minor GC。
大致过程如下:
-
最初一次,当Eden区满的时候,执行Minor GC,将消亡的对象清理掉,并将剩余的对象复制到一个存活区S0(此时,S1是空白的,两个Survivor总有一个是空白的),然后清空Eden区。
-
下次Eden区满了,再执行一次Minor GC,将消亡的对象清理掉,将存活的对象复制到S1中,然后清空Eden区。同时,将S0中消亡的对象清理掉,将其中可以晋级的对象晋级到老年代,将存活的对象也复制到S1区,然后清空S0区。
-
当两个存活区切换了几次(HotSpot虚拟机默认15次,用-XX:MaxTenuringThreshold控制)之后,仍然存活的对象将被复制到老年代。
-
注意:当发生Minor GC的时候,发现Survivor空间不足,部分对象无法复制到Survivor区,虚拟机通过分配担保机制将这些对象提前转移到老年代。
2.2 大对象直接进入老年代
大对象是指,需要大量连续内存空间的java对象。
最典型的的大对象有:很长的字符串以及数组。
虚拟机提供了一个-XX:PretenureSizeThreshold参数,令大于这个设置值的对象直接在老年代分配。
目的:避免在Eden区和两个Survivor区之间发生大量的内存复制(新生代采用复制算法进行GC)。
2.3 长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄计数器。
如果对象在Eden区出生,经过第一次Minor GC之后任然存活,并且能被Survivor区容纳,那么对象年龄设置为1。以后,对象在Survivor区中每“熬过”一次Minor GC,年龄就加1,当对象的年龄达到一定程度(默认15岁),就会晋升到老年区中。
通过参数-XX:MaxTenuringThreshold设置。
2.4 动态对象年龄判定
虚拟机并不是永远要求对象年龄达到设定值才能晋升到老年代。
如果在Survivor空间中的相同年龄(某个年龄值)所有对象大小的总和大于Survivor空间的一半,那么那些年龄大于或者等于该年龄值得对象就可以直接进入老年代。
3、空间分配担保策略
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次Full GC。