GC overhead limt exceed

同学在用Spring写一个医院的管理系统的时候遇到的问题,异常如下:

2019-08-01 13:08:39,922 ERROR [org.springframework.transaction.interceptor.TransactionInterceptor] - <Application exception overridden by rollback exception>
java.lang.OutOfMemoryError: GC overhead limit exceeded
2019-08-01 13:08:42,223 ERROR [org.springframework.transaction.interceptor.TransactionInterceptor] - <Application exception overridden by rollback exception>
java.lang.OutOfMemoryError: Java heap space

在08份39秒的时候抛出了一个OOM,信息是GC overhead limit exceeded;在08.42抛出另一个OOM,信息是Java heap space,真正的发证了内存溢出;

而GC overhead limit exceeded这个信息的OOM并不是就发生了内存溢出,而是JVM在GC overhead limit exceeded通过统计GC的时间来预测是否要OOM,提前抛出异常,防止OOM发生;回收器在回收时间过长的时候会抛出OOM,过长的定义是超过98%的时间来做GC回收了不到2%的内存;

先去查了查,查到的信息无非是修改-XX:-UseGCOverheadLimit禁用预测;或者是直接增加堆的大小, 并没有人去分析为什么GC在那么长的时间内只回收了少量的内存,从而导致内存不够;

在JVM分配对象和做GC的时候:
        对象优先在年轻代
        大对象直接进入老年代,有阈值
        长期存活的对象,一次GC 年龄加1,超过阈值进入老年代
        如果在年轻代中相同年龄的多有对象大小的总和大于年轻代的一半,则年龄大于等于该年龄的直接进入老年代;
        在年轻代Minor GC之前,因为有可能要复制对象到老年代,所以会检查老年代连续的内存是否大于年轻代所有的对象的内存大小,如果大于,证明可以进行Minor GC;
            如果不成立,会检查老年代的连续空间是否大于历次Minor GC的平均大小,如果成立则进行GC;
            如果小于或者,则进行Full GC;
            如果Minor GC失败的话,会紧跟着进行一次Full GC(Ergonomics);
        年轻代分为Eden和两块Survivor,Eden用来存储新建的对象,而两块Survivor的目的是为了减少复制到老年代的对象的次数,进行年龄筛选;当Eden满了之后,触发MniorGC,存活对象会放到其中一块Survivor中,
        清空Eden,再次满的时候清空这块和Eden将对象移到另一块Surivivor中,循环往复,筛选出年龄大的对象,移动到老年代;
        元空间如果分配不够的话 会进行Full GC(Metadata GC Threshold);

对于年轻代和老年代的互相引用,即并发标记的时候发生的变化,虚拟机会将这段时间的对象变价记录在线程Remembered Set Logs 里面,最终合并到Remembered Set中,需要停顿线程,但是可并行执行;

直接增大堆内存并不是最好的解决办法,出现以上问题的原因:

1.有大对象直接进入了老年代,分配过多的时候,不论是生命周期长短,期间GC不掉

2.静态类的生命周期过长,在老年代呆的时间过长,没有被释放

3.年轻代的大小设计不合理,导致很多对象过早的进入了老年代

4.内存使用不当,没有释放资源,或者说在可以节省内存的地方误用,比如说int和Integer对象的大小差别就很大

5.没有及时的帮助GC释放内存,因为在虚拟机执行代码的时候变量占用变量槽,在没有被其他变量复用的时候,一般不会进行垃圾回收,所以显示的进行资源释放有利于垃圾回收,但是不是必要的,最好就是控制变量的作用域;

6.类似ThreadLocal的对象使用不当,在ThreadLocal的Map中是作为原引用的弱引用存在的,当使用ThreadLoal的时候,如果ThreadLocal原对象不在有强引用,则相应的Map中的弱引用会被垃圾回收,回收后就称为null,但是存放的对象并不会被回收,相应的就占用了一部分空间;所以不能在使用ThreadLocal的时候将之置为null,而使用get和set方法的时候会每次清除key为null的entry,可以避免内存泄漏;如果使用静态的ThreadLocal的话,因为静态常量的引用是和类一起卸载的,所以会延迟生命周期,容易造成内存泄漏;
 


 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值