当前位置 博文首页 > 醒然自然醒的博客:一看就懂! 新生代的垃圾回收算法
1.复制算法: Eden区和Survivor区
一个 Eden区,两个survivor区,eden区占80%内存空间,每一块survivor区占 10%。
平时使用一块eden和一块survivor区所以内存使用率为 90%。
? 刚开始对象都分配在eden区,如果eden区快满了就触发垃圾回收,把eden区中的存活对象转移到一块空着的survivor区,eden区清空,然后再次分配新对象到eden区,再触发垃圾回收,就把eden区存活的和survivor区存活的转移到另一块空着的survivor。
这么设计的原因: 每次垃圾回收可能存活下来的对象就1%,如果eden+一块survivor满了900MB,一次垃圾回收下来有10MB存活,就把10MB转移到另一块survivor区。始终保持一块survivor区是空着的。这样可以控制内存碎片,而且内存的使用率都很高。
问题:
解答
比如 static B b = new B();
静态变量b会一直引用B对象,这类对象不会被回收掉,每在新生代里躲过一次gc被转移到一块Survivor区时,年龄就长一岁。
默认的设置是达到15岁时转移到老年代。
也可以通过jvm参数(年龄阈值)"-XX:MaxTenuringThreshold"设置
2.动态对象年龄判断
有另一个规则可以让对象早点进入老年代:动态对象年龄判断
触发时机:发生Minor Gc 后,将存活的对象移动到空闲的 Survivor区时触发
描述:虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。
举例子:图里一岁和二岁的对象加起来>=survivor区的一半50MB ,那么survivor2区里年龄大于等于2岁的对象要提前进入老年代。
3. 大对象直接进入老年代
有一个jvm参数 "-XX:PretenureSizeThreshold "可以把他的值设为字节数。比如 1048576字节,就是1MB
如果你创建了一个大于这个大小的对象,比如一个超级大的数组,就直接把这个大对象放到老年代里。不会经过新生代。
之所以这么做,就是要避免新生代出现大对象,然后屡次躲过GC,还要把他在两个Survivor区域里来回复制多次之后才进入老年代。
4. 空间分配担保机制
survivor区内存比较小,所以尽可能保证在一次Minor gc后,如果survivor区放不下,老年代要放得下,所以在Minor gc前要先计算老年代的可用空间够不够,能不能兜底。
抛几个问题:
1.什么是空间分配担保?
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,
如果大于,则此次Minor GC是安全的
如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
2.为什么要进行空间担保?
是因为新生代采用复制收集算法,假如大量对象在Minor GC后仍然存活(最极端情况为内存回收后新生代中所有对象均存活),而Survivor空间是比较小的,这时就需要老年代进行分配担保,把Survivor无法容纳的对象放到老年代。老年代要进行空间分配担保,前提是老年代得有足够空间来容纳这些对象,但一共有多少对象在内存回收后存活下来是不可预知的,因此只好取之前每次垃圾回收后晋升到老年代的对象大小的平均值作为参考。使用这个平均值与老年代剩余空间进行比较,来决定是否进行Full GC来让老年代腾出更多空间。
问题:Minor Gc后的对象太多无法放入Survivor区怎么办?
假如在发生gc的时候,eden区里有150MB对象存活,而Survivor区只有100MB,无法全部放入,这时就必须把这些对象全部直接转移到老年代里。
问题: 接着上面的问题,如果这时老年代的可用内存小于新生代全部对象大小,万一Minor gc后新生代的对象都存活下来,然后需要全部转移到老年代,但是老年代空间不够,怎么办?
理论上有这个可能。
这时如果设置了 "-XX:-HandlePromotionFailure"的参数,就会尝试判断,看老年代内存大小是否大于之前每一次Minor gc后进入老年代的对象的平均大小。
比如说,之前Minor gc 平均10M左右的对象进入老年代,此时老年代可用内存大于10MB,那么大概率老年代空间是足够的。
如果上面那个判断失败,或者是根本没设置这个参数,那就直接触发"Full GC",对老年代进行垃圾回收,腾出些空间,再Minor gc。
如果判断成功了,那么大概率老年代内存是够的,就冒风险尝试Minor gc。这时有以下几种可能。
Full gc 就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。
如果 Full gc后老年代还是没有足够的空间存放剩余的存活对象,那么就会导致 “OOM” (out of memory) 内存溢出。
所以Minor gc 触发要先对老年代空间做检查看看老年代空间够不够。检查失败的时候触发"Full Gc"给老年代腾空间,或者Minor gc 后剩余对象太多放入老年代内存都不敢,也要触发"Full Gc"。
总结:触发老年代垃圾回收(Full gc) 的时机
一张图总结一下
根据上图回答问题