本篇内容来自深入理解java虚拟机_JVM高级特性和最佳实践

JVM内存分配策略

java的内存自动管理可以总结为自动分配和自动回收内存。
对象内存的分配主要是在堆上分配内存,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程有限分配在TLAB上。少数情况可以直接分配在老年代中。
TLAB技术:JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)。Java程序中很多对象使用过一次就会被销毁,这些小对象也不存在线程共享会很快被GC回收,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。
回收请看:JVM垃圾回收算法
普遍的几条内存分配规则:

  • 对象优先在Eden上分配
  • 大对象直接进入老年代
  • 长期存活的对象将进入老年代
  • 动态对象年龄判定
  • 空间分配担保

发生在新生代的GC称为MinorGC,由于新生代对象很多具有使用一次就不再使用的特性,MinorGC会发生的比较频繁,回收速度也会比较快。
发生在老年代的GC,称为MajorGC或者FullGC,其实两者也稍有不同,但我们一般认为他两个词是一种意思。一般发生MajorGC也会伴随至少一次MinorGC(并非绝对,在Parallel Scavenge 收集器的收集策略里就有直接进行MajorGC的策略选择过程)。MajorGC的速度一般会比MinorGC慢10倍,但是MajorGC发生频率没有MinorGC频繁。

对象优先分配在Eden上

一般情况下对象会分配到新生代Eden上,当Eden没有足够空间进行分配时,虚拟机JVM将进行一次MinorGC。

大对象直接进入老年代

需要大量连续内存空间的java对象称为大对象例如字符串和数组。大对象对于分配内存来说不是个好消息,如果大对象能够再分配内存后频繁使用那还不算坏,如果是很多只使用一次的大对象,为了能够提供连续的内存空间来存储这些大对象,就需要频繁的进行垃圾回收,所以应减少使用很多只使用一次的大对象。
为了对大对象的内存分配进行优化,JVM提供了-XX:pretenureSizeThreshold,大于这个数的对象会被直接分配到老年代,可以避免Eden区和两个Survivor区之间大量的对象复制。因为如果不直接分配到老年代中,很多大对象会从Eden复制到Survivor,然后再分配到老年代(如果最终能分配到老年代)。
注意:-XX:pretenureSizeThreshold只对Serial和ParNew两款垃圾收集器有效。

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

JVM的分代垃圾收集思想要区分识别哪些对象应该分配到新生代,哪些对象应该分配到老年代,为了实现这个功能,JVM给每个对象定义了一个对象年龄(Age)计数器,如果对象在Eden出生并经过第一次MinorGC后仍然存活,并且Survivor有空间可以存放,这个对象将被移动到Survivor中,并且对象年龄设置为1.对象在Survivor中每经历过一次MinorGC如果仍然存活age增加1,当age增加到一定程度,对象将被放入老年代。
设置-XX:MaxTenuringThreshold参数可以对age进入老年代的阈值进行设置。

动态对象年龄判断

上面说了对象的age要到达XX:MaxTenuringThreshold设置的参数后才能被存放到老年代,但JVM为了适应不同程序的内存状况,它并不是永远的要求对象的年龄必须到XX:MaxTenuringThreshold,如果在Survivor空间中相同年龄所有对象大小的综合大于Survivor空间的一般,年龄大于或者等于该年龄的对象就可以直接进入老年代。

空间分配担保

在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果是,那么MinorGC可以确保是安全的,如果不是JVM会查看HandlePromotionFailure设置值是否允许担保失败,如果允许JVM会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于将尝试一次MinorGC,如果小于或者设置不允许HandlePromotionFailure,这时就会进行一次FullGC。
为什么要尝试进行一次MinorGC呢?有什么风险?
新生代使用复制收集算法,但为了提高内存使用率,只使用了一个Survivor作为备份存储内存,当出现当量对象在MinorGC后仍然存活时(极端情况是内存回收后新生代中对象全存活),就需要老年代进行分配担保,把Survivor无法容纳的对象进入到老年代中,老年代要进行担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会醋挪下来在实际完成之前无法明确知道,所以只好取之前每一次回收晋升到老年代对象哦让你过量的平均值作为经验值,和老年代中的剩余空间进行比较,决定是否进行FullGC来让老年代腾出更多空间。
担保是可能失败的,就是这次需要存入的对象远大于预估值,这时担保失败,对象也无法存入老年代,需要重新发起一次FullGC。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐