java对象内存的分配,从概念上讲都应该分配在堆上分配,实际有可能经过即时编译后被拆散为标量类型并间接地在栈上分配。新生对象通常分配在新生代中,少数情况下可能会直接分配在老年代(比如对象大小超过一定阀值)。对象的分配规则并不是固定的,取决于虚拟机使用哪一种垃圾收集器,以及虚拟机中与内存相关的参数设定。

使用HotSpot虚拟机,以客户端模式运行,使用Serial+Serial Old客户端默认收集器组合的内存分配和回收策略,最基本的几条内存分配原则。

1. 对象优先在Eden分配

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

2. 大对象直接进入老年代

大对象就是指需要大量连续内存空间的java对象,最典型的大对象便是那种很长的字符串或者元素很多的数组。

写程序时要尽量避免短命的大对象,因为在分配空间时,容易导致内存明明还有不少空间时提前触发垃圾回收,以获取足够的连续空间才能安置好它们,而垃圾回收复制大对象时意味着高额的内存复制开销。

HotSpot提供了-XX:PretenureSizeThreshold参数,指定大于该设置值的对象直接分配在老年代,避免在Eden区和两个Survivor区之间来回复制,产生大量的内存复制操作。

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

对象一般在Eden区诞生;经过第一次Minor GC后,如果对象还存活,并能被Survivor容纳,对象将会被移到Survivor;对象在Survivor中每熬过一次Minor GC,年龄增加1;当对象的年龄达到15时,会被移到老年代。可以通过-XX:MaxTenuringThreshold设置,默认值是15。

4. 动态对象年龄判定

如果在Survivor空间中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接移到才年代,无须等到-XX:MaxTenuringThreshold要求的年龄。

5. 空间分配担保

  • 在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,这一次的Minor GC可以确保是安全的。
  • 如果不成立,虚拟机会查看-XX:HandlePromotionFailure参数设置是否允许担保失败
  • 如果允许,继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小
  • 如果大于,Minor GC
  • 如果小于,Full GC
  • 如果不允许,Full GC
  • 如果成立,Minor GC