《深入理解Java虚拟机》2.垃圾回收_内存分配与回收策略

>> 内存分配与回收策略

1.1对象优先在Eden分配

大多数情况下,对象在新生代Eden区分配,当Eden中没有空间进行分配时,虚拟机将发起一次Minor GC(只回收新生代)

HotSpot虚拟机提供了-XX:+PrintGCDetails这个收集器日志参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况。

默认情况下,新生代Eden : s0 : s1 = 8 : 1 : 1

1.2大对象直接进入老年代

大对象指需要大量连续内存空间的Java对象,大对象对虚拟机的内存分配来说是一个不折不扣的坏消息,比遇到一个大对象更坏的消息就是遇到一群朝生夕灭的短命大对象。在Java虚拟机中要避免大对象的原因是,在分配空间时,它容易导致内存明明还有不少空间时就提前触发垃圾收集,以获取足够的连续空间才能安置好他们,而当复制对象时,大对象就意味着高频的内存复制开销。

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

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

HotSpot虚拟机中多数收集器都采用了分带收集来管理堆内存,那么内存回收时就必须能决策哪些存活对象应该放在新生代,哪些存活对象放在来年代。

为做到这点,虚拟机为每个对象定义了一个对象年龄(Age),放在对象头中。

对象通常在Eden中产生,如果经过第一次Minor GC后仍然存活,并且能被Survivor区容纳的话,该对象就会被移动到Survivor区,并将其年龄设置为1岁,对象在Survivor区中每熬过一次Minor GC,年龄就加一,当它的年龄增加到一定程度时(默认为15),就会晋升到老年代。

1.4动态对象年龄判定

为了能适应不同程度的内存状况,HotSpot虚拟机并不是永远要求对象的年龄必须达到指定的阈值才能晋升到老年代,如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象就可以直接晋升老年代。

1.5空间分配担保

在发生Minor GC前,虚拟机必须先检查老年代最大可用的连续内存空间是否大于新生代所有对象的空间,如果这个条件成立,那这一次的Minor GC可以确保是安全的,如果不成立,则虚拟机会先查看-XX:HandlePromotionFailure参数的设置是否允许担保失败,如果允许,那会检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于将尝试进行一次Minor GC,尽管这个Minor GC是有风险的,如果小于或者-XX:HandlePromotionFailures设置为不允许冒险,那么将会进行一次Full GC.

解释一下"冒险"是什么意思

新生代使用复制收集算法,但为了内存利用率,只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况——最极端的情况就是内存回收后新生代中所有对象都存活,需要老年代进行分配担保,把Survivor无法容纳的对象直接送入老年代,这与生活中贷款担保类似。老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,但一共有多少对象会在这次回收中活下来在实际完成内存回收之前是无法明确知道的,所以只能取之前每一次回收晋升到老年代对象容量的平均大小作为经验值,与老年代的剩余空间进行比较,决定是否进行Full GC来让老年代腾出更多空间。

取历史平均值来比较其实仍然是一种赌概率的解决办法,也就是说假如某次Minor GC存活后的对象突增,远远高于历史平均值的话,依然会导致担保失败。如果出现了担保失败,那就只好老老实实地重新发起一次Full GC,这样停顿时间就很长了。

虽然担保失败时绕的圈子是最大的,但通常情况下都还是会将 -XX:HandlePromotionFailure 开关打开,避免Full GC过于频繁。

猜你喜欢

转载自blog.csdn.net/qq_46312987/article/details/121214442