JVM内存分配策略

2021-06-19  本文已影响0人  云芈山人

Java技术体系中所提倡的自动内存管理最终可归结为自动化解决两个问题:给对象分配内存以及回收分配给对象的内存。

对象的内存分配,大方向讲,就是堆上分配(也可能经过JIT编译后拆散为标量类型并间接地栈上分配),对象主要分配在新生代的Eden区上,如果启动本地线程分配缓冲,将按线程优先在TLAB上分配,少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定,其细节取决于当前使用哪一种垃圾收集器组合。

堆内存.png

对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配,当Eden区没足够空间进行分配时,Java虚拟机将发起一次Minor GC。

新生代GC(Minor GC):指发生在新生代的垃圾收集动作,因Java对象大多都具备朝生夕死的特性,所以Minor GC非常频繁,一般回收速度也比较快。

Minor GC的过程(复制--清空--互换)复制算法:
第一步:把 Eden 和From Survivor区域中存活的对象复制到 To Survivor 区域(如果有对象的年龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果 To Survivor 不够位置了,就通过分配担保机制提前转移到老年区);
第二步:清空Eden、From Survivor中的对象;
第三步:From Survivor与To Survivor位置互换,原先的To Survivor成为下一次GC的From Survivor区域(From Survivor作为)。

老年代GC(Major GC/Full GC):只发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上,因为是扫描再回收。
Major GC采用标记清楚算法,首先扫描一次所有老年代对象,进行标记,回收没有标记的对象,会产生内存碎片。

相关参数
-XX:+PrintGCDetails:这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况
-XX:SurvivorRatio=8:定义新生代中Eden与一个survivor的空间比例为8:1
-Xms、-Xmx:分配用来设置进程堆内存的最小大小和最大大小
-Xmn:用来设置堆内新生代的大小。通过这个值我们也可以得到老生代的大小:-Xmx减去-Xmn
-Xss:设置每个线程可使用的内存大小。
-XX:PermSize、-XX:MaxPermSize:分配用来设置永久代的最小大小和最大大小。Java 8以后移除了方法区,取而代之的是本地元空间Metaspace,大小由-XX:MetaspaceSize和-XX:MaxMetaspaceSize调节
-XX:NewRatio:设置老生代和新生代的比值,例如该值为3,则表示新生代与老生代比值为1:3

大对象直接进入老年代

所谓大对象是指需大量连续内存空间的Java对象,最典型的就是很长的字符串一级数组,它对于虚拟机内存管理是一个坏消息,特别是一群“朝生夕死”的“短命大对象”,写程序时应尽量避免。经常出现容易导致还有不少空间时就提前触发垃圾收集以获得足够连续空间来安置他们。

相关参数
-XX:PertenureSizeThreshold:令大于设置值的对象直接在老年代分配,避免Eden和两个Survivor区之间的大量内存复制(只对Serial和ParNew两款收集器有效)

长期存活的对象进入老年代

虚拟机采用分代收集思想管理内存,那么内存回收时必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为做到此点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。

若对象在Eden出生并经过第一次的Minor GC后仍然存活,并能被Survivor容纳,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄增1,年龄到一定程度(默认为15),就会晋升到老年代中。

相关参数
-XX:MaxTenuringThreshold:设置转入老生代的存活次数。如果是0,则直接跳过新生代进入老生代

动态对象年龄判断

为更好适应不同程度内存状况,虚拟机并不是永远要求对象年龄达到MaxTennuringThreshold才能晋升到老年代。若在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就直接进入老年代,无须等到参数要求的年龄。

空间分配担保

在发生Minor GC之前,虚拟机会做以下的操作:


空间分配担保.png

图中的“冒险”是什么?
新生代使用复制手机算法,但为了内存使用率,只用一个Survivor来作为轮换备份,因此当出现大量对象在Minor GC后,就需老年代进行分配担保,把Survivor无法容纳的对象直接进入老年代。
老年代进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来来实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值。
取平均值其实仍然是一种动态概率的手段。若担保失败,只好在失败后重新发起一次Full GC。虽失败时绕的圈子是最大的,但大部分情况下都还是将HandlePromotionFailure开关打开,避免Full GC过于频繁。

JDK6 Update 24之后,HandlePromotionFailure参数不会再影响到虚拟机的空间分配策略。通过查看源码变化,虽还定义了HandlePromotionFailure参数,但在代码中已不会再使用它。规则变为:只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC。

上一篇 下一篇

猜你喜欢

热点阅读