暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

程序内存溢出问题分析

IT那活儿 2022-04-22
830
点击上方“IT那活儿”,关注后了解更多内容,不管IT什么活儿,干就完了!!!




场景来源

户现场有一套重要的业务系统,供业务同事进行使用,但是每连续运行10天都会出现问题,会造成程序慢响应,无响应情况,严重影响消费者、使用者的使用体验,经常得到业务同事反馈,所以配合研发对该问题进行查询。

该系统硬件服务器性能足够,但每个重启周期内内存都是缓慢上升,观察JVM使用率、系统内存使用率等均是持续上升,未发现有主动进行内存回收,初步怀疑为内存泄漏导致。




初识JVM虚拟机


Java语言是一种计算机编程语言,拥有跨平台,面向对象,泛型编程的特性,广泛应用于企业级Web应用的开发和移动应用的开发。
JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java程序是如何实现跨平台运行的呢?
主要就是通过JVM虚拟机来运行的。




JVM虚拟机的结构


Java虚拟机主要分为五大模块:

  • 类装载器子系统
  • 运行时数据区
  • 执行引擎
  • 本地方法接口
  • 垃圾收集模块
其中垃圾收集模块在Java虚拟机规范中并没有要求Java虚拟机垃圾收集,但是在没有发明无限的内存之前,大多数JVM实现都是有垃圾收集的。而运行时数据区都会以某种形式存在于每一个JAVA虚拟机实例中,但是Java虚拟机规范对它的描述却是相当抽象。
这些运行时数据结构上的细节,大多数都由具体实现的设计者决定。
当我们程序运行后会申请内存空间,其中java栈本地方法栈,程序计算器是JVM虚拟机自动管理的,我们可以管理的区域是堆区和方法区,经常说的java内存调优调的就是这两个区域。



常见的内存溢出


现象一:

java.lang.OutOfMemoryError: Java heap space
该报错为堆溢出,这种场景最为常见主要原因可能有:
  • 代码中可能存在大对象分配。

  • 可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

解决方法:
  • 检查是否存在大对象的分配,最有可能的是大数组分配。

  • 如果没有找到明显的内存泄露,可以临时 -Xmx 加大堆内存。

  • 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性。

现象二:

java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace
永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。
JDK8后,元空间替换了永久代,元空间使用的是本地内存。该报错为永久代/元空间溢出,可能原因有如下几种:
  • 在Java7之前,频繁的错误使用String.intern()方法 。

  • 运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载 。

  • 应用长时间运行,没有重启。

解决方法:
  • 检查是否永久代空间或者元空间设置的过小。

  • 检查代码中是否存在大量的反射操作 。

  • dump之后通过mat检查是否存在大量由于反射生成的代理类。

现象三:

java.lang.OutOfMemoryErrorGC overhead limit exceeded
这是JDK6新加的错误类型,一般都是堆太小导致的。
Sun 官方对此的定义:超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。
解决方法:
  • 检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。

  • 添加参数 -XX:-UseGCOverheadLimit  禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。

现象四:

java.lang.OutOfMemoryError : unable to create new native Thread

出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。

解决方法:
  • 通过 -Xss 降低的每个线程栈大小的容量。

  • 线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:

/proc/sys/kernel/pid_max    /proc/sys/kernel/thread-max    maxuserprocess(ulimit -u)    /proc/sys/vm/maxmapcount
现象五:
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
这种情况一般是由于不合理的数组分配请求导致的,在为数组分配内存之前,JVM 会执行一项检查。要分配的数组在该平台是否可以寻址(addressable),如果不能寻址(addressable)就会抛出这个错误。
解决方法:
检查你的代码中是否有创建超大数组的地方。
现象六:
java.lang.OutOfMemoryError: Out of swap space
这种情况一般是操作系统导致的,可能的原因有:
  • swap 分区大小分配不足。

  • 其他进程消耗了所有的内存。

解决方案:
  • 其它服务进程可以选择性的拆分出去 。

  • 加大swap分区大小,或者加大机器内存大小。




紧急处理方法


程序挂掉运维层面可以从系统message日志中查看是否有OOM杀掉进程报错;如果有第一现场,在条件允许的情况下,可以dump内存使用情况,通过工具分析可以更直观的了解内存具体使用到了那些地方。
当然,如果能确认为内存溢出情况,万能重启大法可以先临时解决供业务恢复,等代码修复上线后再从根本上修复问题。

本文作者:臧二飞

本文来源:IT那活儿(上海新炬王翦团队)

​​​
文章转载自IT那活儿,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论