Linux环境多线程报错Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded原因解决以及分析

今天线上的项目停止服务,导致用户登录不上,上次也导致了服务停止,没有把重心放在上面,今天又出现了这个问题才一直在找问题,查看日志定位到发生问题的地方,发现出现了java.lang.OutOfMemoryError: GC overhead limit exceeded错误。

那么这种原因是怎么来的?

这个oracle官方已经给出了这个错误产生的原因和解决方法:

在这里插入图片描述
翻译过来的意思是:

线程名称中的异常:java。lang.OutOfMemoryError:超出GC开销限制

原因:详细消息“超出 GC 开销限制”表示垃圾收集器一直在运行,Java 程序进展非常缓慢。在垃圾收集之后,如果Java进程花费大约 98% 以上的时间进行垃圾收集,并且如果它正在恢复堆的不到 2%,并且到目前为止一直在进行最后5次(编译时常量)连续垃圾收集,则Java进程将停止。将抛出 lang.OutOfMemoryError。通常会引发此异常,因为活动数据的数量几乎无法放入 Java 堆,因此新分配的可用空间很小。

操作:增加堆大小,超出 GC 开销限制的lang.OutOfMemoryError异常可以使用命令行标志-XX:-UseGCOverdeLimit关闭。

简单来说,java.lang.OutOfMemoryError: GC overhead limit exceeded 发生的原因是,当前已经没有可用内存,经过多次 GC 之后仍然没能有效释放内存。

原因

JVM 的 GC 过程会因为 STW,只不过停顿短到不容易感知。当引起停顿时间的 98% 都是在进行 GC,但是结果只能得到小于 2% 的堆内存恢复时,就会抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded这个错误。

示例图:

在这里插入图片描述
这种机制也会有一些问题,就是被占用的内存,经过多次长时间的 GC 操作都无法回收,导致可用内存越来越少,俗称内存泄露,JVM 就会报java.lang.OutOfMemoryError: GC overhead limit exceeded错误。

如果没有这个异常,会出现什么情况呢?经过垃圾回收释放的 2% 可用内存空间会快速的被填满,迫使 GC 再次执行,出现频繁的执行 GC 操作, 服务器会因为频繁的执行 GC 垃圾回收操作而达到 100% 的时使用率,服务器运行变慢,应用系统会出现卡死现象,平常只需几毫秒就可以执行的操作,现在需要更长时间,甚至是好几分钟才可以完成。

下面给个示例实现一下

示例

	 Map map = System.getProperties();
 		    Random r = new Random();
 		    while (true) {
 		      map.put(r.nextInt(), "value");
 		    }

然后设定堆内存是100m,比如java -Xmx100m -XX:+UseParallelGC Wrapper。不同的系统环境可能需要设置不同的堆内存大小,否则会产生不同的 OOM 错误。因为上面的错误产生条件是 98% 的时间和 2% 的内存。如果其中一个没有达到条件,结果 Map 对象扩容就可能出现java.lang.OutOfMemoryError: Java heap space这个错误。

你会发现没有运行之前 CPU 如下图:

在这里插入图片描述
运行之后 CPU 如下图:

在这里插入图片描述

解决方法

  • 增加 heap 堆内存
  • 增加对内存后错误依旧,获取 heap 内存快照,使用 Eclipse MAT 工具,找出内存泄露发生的原因并进行修复
  • 优化代码以使用更少的内存或重用对象,而不是创建新的对象,从而减少垃圾收集器运行的次数。如果代码中创建了许多临时对象(例如在循环中),应该尝试重用它们
  • 升级 JDK 到1.8,最起码也是 1.7,并使用 G1 GC 垃圾回收算法
  • 除了使用命令 -xms1g - xmx2g 设置堆内存之外,尝试在启动脚本中加入配置:
    • -XX:+UseG1GC -XX:G1HeapRegionSize=n -XX:MaxGCPauseMillis=m
    • -XX:ParallelGCThreads=n -XX:ConcGCThreads=n
  • 在bin/catalina.sh增大Xms和Xmx:
    • -JAVA_OPTS=””-Xms512m -Xmx4096m -XX:MaxPermSize=128m -XX:-UseGCOverheadLimit -XX:+UseConcMarkSweepGC””
    • --Xms512m 初始的java内存堆大小 256M
    • -Xmx4096m 最大的java内存堆大小 2048M
    • -XX:PermSize=128m GC预留的内存,假如你的应用有大量的Class被动态载进或卸载,你应该不这个参数设大些
    • -XX:MaxPermSize=256m 最大的GC预留内存
    • -Dsun.rmi.dgc.client.gcInterval=3600000 RMI客户端GC发生周期的设定
    • -Dsun.rmi.dgc.server.gcInterval=3600000 RMI服务端GC发生周期的设定

    我的解决方法

    我的项目是在 jdk1.8 64 位操作系统上运行的,服务器物理内存 64G,内存足够,分配的 4G 的内存,并且运行了稳定运行了一年,后来出现了异常服务停止的问题,是完善代码解决了问题,现在又出现了两次服务停止的问题。

    我在想又是代码的原因,查看日志定位到问题的点是哪个方法出现了问题,因为我在循环方法中大量的创建对象并且使用了多线程,而且数据量很大,每次都从数据库中查询数据,这些问题都会导致占用内存,又不能被 GC 回收,久而久之就出现内存不足,曝出 GC overhead limit exceeded。

    所以发生 GC overhead limit exceeded 的问题归根结底还是我们代码的问题,和内存无关,优化提升性能首先从代码调优开始。

    • 4
      点赞
    • 16
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值