一、对象优先在新生代Eden区中分配
首先演示一个简单的场景来了解一下对象在内存上的大致分配情况
前置条件:
Java堆:20M并且不可扩展
新生代:10M(Eden区与一个Survivor区的空间比例是8:1),即Eden区8M、from space 1M/to space 1M。因此新生代可用空间为9M。
老年代:10M
4个待分配对象:A(2M)、B(2M)、C(2M)、D(4M)
接下来依次为对象ABCD分配内存,当分配D对象的时候,发现Eden区已经被ABC对象占用了6M内存,剩余的3M可用内存不足以分配给D对象,于是发生一次Minor GC(新生代GC)。GC过程中又发现,ABC对象都无法转移到Survivor区,所以只能通过分配担保机制将ABC提前放入到老年代。
image.png
本次GC结束后D对象成功被分配到Eden区,此时内存分配情况是:
Eden被占用4M(D对象)、Survivor区空闲、老年代被占用6M(ABC三个对象)
image.png
二、大对象直接进入老年代
大对象是指需要占用大量连续内存空间的Java对象,典型的大对象就是很长的字符串以及数组。
大对象对虚拟机的内存分配来说就是一个坏消息,因为需要腾出一大块连续的内存空间用来安置它们,而想要获取足够大的连续内存空间势必会提前触发垃圾回收机制,因此我们应该尽量避免大对象的出现,尤其是“短命大对象”。
虚拟机通常会提供参数- XX:PretenureSizeThreshold,使大于这个值的对象直接分配给老年代,这样就可以避免Eden区与Survivor区之间发生大量的内存复制。
三、长期存活的对象将进入老年代
内存回收时,为了识别出哪些对象应该放在新生代,哪些应该放在老年代,虚拟机给每个对象定义了一个年龄计数器。
如果对象在Eden区经历一次Minor GC仍然存活,并且能够被Survivor区容纳,那么它将被移动到Survivor区并且年龄设为1。此后对象在Survivor区每经历一次Minor GC并且存活下来,年龄就增加1,当年龄增加到一定值对象就会进入到老年代中。
四、动态对象年龄判定
为了更好地适应内存使用情况,虚拟机并不是严格地要求对象必须达到一定年龄值才会进入到老年代中。如果在Survivor空间内,相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象都可以直接进入到老年代中。
五、空间分配担保
1、检查老年代最大连续可用空间是否大于新生代所有对象的总和。如果条件成立,那么Minor GC是安全的;如果不成立,进入步骤2;
2、检查HandlePromotionFailure设置值是否允许担保失败,如果允许,那么继续检查老年代可用连续空间是否大于历次晋升到老年代的所有对象平均大小。如果大于,就尝试着进行一次Minor GC,尽管这样做是有风险的;如果小于或者HandlePromotionFailure设置为不允许冒险,那么会进行一次Full GC。
解释一下是冒了什么风险:前面提到过新生代使用复制算法收集回收,为了内存利用率我们只使用一个Survivor空间作为轮换备份,因此当Minor GC后仍然有大量对象存活,而此时Survivor空间无法容纳这么多对象时,就需要老年代进行分配担保,把无法转移到Survivor空间的对象直接转移到老年代上。前提是老年代必须保证自己有足够的空间来进行这样的担保,因为有多少对象存活下来是无法提取预知的,所以解决方案是取之前每一次回收晋升到老年代的对象容量的平均大小值作为经验值,与老年代剩余可用空间进行比较,来决定是否要执行Full GC腾出老年代空间。
网友评论