Java虚拟机学习笔记-内存分配与回收策略总结

写在前面

  总结Java虚拟机的内存分配和回收策略。对象的分配,粗略的讲就是在Java堆上进行分配,主要是Eden空间。少数情况也可能直接分配到老年代。需要注意的是,这篇笔记用于记录的是Serial / Serial Old收集器下的内存分配与回收策略。


对象优先在Eden分配

  大多数情况下,会在Eden空间分配。如果Eden空间不够时,会进行一次Minor GC,也就是发生在新生代的收集动作,非常频繁,回收速度很快。回收时,会将Eden空间中存活的对象复制到To Survivor空间,如果此时To Survivor空间不足,将会通过担保分配提前转移到老年代。


大对象直接进入老年代

  需要大量连续内存空间的Java对象,称为大对象,比如很长的字符串或者数组。大对象对虚拟机的内存分配不是一个好消息,更坏的消息是这个大对象还是“朝生夕死”的,所以写程序时应该避免使用“朝生夕死”的大对象。

  虚拟机提供了一个参数-XX:PretenureSizeThreshold参数,可以通过设置这个参数的值,来使大于该值的对象直接进入老年代。


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

  虚拟机给每个对象都定义了一个对象年龄计数器,对象在Eden空间出生,并且经过第一次Minor GC后仍然存活,被移动到Survivor空间中,将对象年龄设置为1。到了指定的年龄将进入老年代,默认是15岁。当然,我们也可以通过参数-XX:MaxTenuringThreshold设置这个晋升老年代的年龄。


动态对象年龄判定

  为了更好的适应不同程序的内存状态,虚拟机并不是永远都要求对象到了指定的年龄才能进入老年代。如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,则大于或等于该年龄的对象可以直接进入老年代。


空间分配担保

  在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果成立,则说明此次Minor GC是安全的。如果不成立,虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试着进行一次Minor GC,尽管是有风险的,因为不能保证某次存活的对象数量突增;如果小于,或者设置为不允许,这时就要改为进行一次Full GC。

  在JDK 6 Update 24之后,规则变为只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,否则将进行Full GC(也就是发生在老年代的垃圾收集动作,经常会伴随至少一次的Minor GC,但不是绝对的)。


小结

  总结一下,新的对象会在Eden空间分配内存,当Eden空间内存不够时,会触发一次Minor GC,将存活的对象复制到To Survivor空间,清理Eden空间,此时这些对象的年龄为1。然后,原来的From Survivor将成为新的To Survivor,To Survivor将变为From Survivor,下次Minor GC发生时,From Survivor中存活的对象年龄如果到了指定的年龄阀值,将会进入老年代,未达到的复制到To Survivor空间,Eden空间存活的对象也复制到To Survivor,年龄增加1,并回收Eden空间和From Survivor空间的内存,然后再次交换From Survivor和To Survivor的角色。当然,如果To Survivor空间小于所有存活的对象,那么这些存活的对象会通过分配担保机制提前进入老年代。


参考资料

Author: HowieLi
Link: https://www.howieli.cn/posts/java-memory-allocation-or-collection-tactics.html
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.