新生代的垃圾回收算法

一看就懂! 新生代的垃圾回收算法

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区是空着的。这样可以控制内存碎片,而且内存的使用率都很高。

问题:

  • 万一存活下来的对象超过10%内存空间,在另外一块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。这时有以下几种可能。

  • Minor Gc 后,剩余的存活对象大小,小于Survivor区,那就直接进入Survivor区。
  • MInor Gc 后,剩余的存活对象大小,大于Survivor区,小于老年代可用内存,那就直接去老年代。
  • Minor Gc后,大于Survivor,老年代,很不幸,就会发生"Handle Promotion Failure"的情况 ,触发"Full GC"。

Full gc 就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。

如果 Full gc后老年代还是没有足够的空间存放剩余的存活对象,那么就会导致 “OOM” (out of memory) 内存溢出。

所以Minor gc 触发要先对老年代空间做检查看看老年代空间够不够。检查失败的时候触发"Full Gc"给老年代腾空间,或者Minor gc 后剩余对象太多放入老年代内存都不敢,也要触发"Full Gc"。

总结:触发老年代垃圾回收(Full gc) 的时机

  • Minor gc 前,检查一下发现之前的 Avg(进入老年代对象的大小)> 现在老年代可用空间,提前触发Full gc。
  • Minor gc 后,发现剩余对象 > 老年代可用空间,触发Full gc。

一张图总结一下
在这里插入图片描述

根据上图回答问题

  1. 什么时候会触发Minor gc?
  2. 触发Minor gc 之前会如何检查老年代的大小,涉及哪几个步骤和条件?
  3. 什么时候在Minor Gc 之前就会提前触发一次 Full gc?
  4. Minor gc 过后可能对应那几种情况?
  5. 哪些情况下Minor gc 的对象会进入老年代?
  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值