首页 > 其他分享 >OOM原因及解决方案

OOM原因及解决方案

时间:2023-12-26 11:38:25浏览次数:26  
标签:ByteBuffer OOM 解决方案 线程 内存 JVM class 原因

oom作为研发最常见,也是最难定位的问题,最常见的原因:

  • 本身JVM资源不够或者资源耗尽
  • 申请的太多线程,外部请求量激增

一、oom具体原因

jvm因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时, 就会抛出 java.lang.OutOfMemoryError: ··· (注意: 这是个很严重的问题, 因为这个问题已经严重到不足以被应用处理)。

具体原因大致为两方面:

  • 自身原因: 比如虚拟机本身可使用的内存太少。而正常业务使用了大量内存
  • 外在原因: 如应用使用的太多, 且用完没释放, 浪费了内存。此时就会造成内存泄露或者内存溢出。对象的申请未释放;系统资源频繁申请,耗尽如网络请求。

具体为:

  • 内存泄露: 申请使用完的内存没有释放, 导致虚拟机不能再次使用该内存, 此时这段内存就泄露了, 因为申请者不用了, 而又不能被虚拟机分配给别人用。
  • 内存溢出: 申请的内存超出了 JVM 能提供的内存大小, 此时称之为溢出。

二、常见 OOM 情况

  • java.lang.OutOfMemoryError: Java heap space ------>java 堆内存溢出, 此种情况最常见, 一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露, 需要通过内存监控软件查找程序中的泄露代码, 而堆大小可以通过虚拟机参数 - Xms,-Xmx 等修改。
  • java.lang.OutOfMemoryError: PermGen space ------>java 永久代溢出, 即方法区溢出了, 一般出现于大量 Class 或者 jsp 页面, 或者采用 cglib 等反射机制的情况, 因为上述情况会产生大量的 Class 信息存储于方法区。当出现此种情况时可以通过更改方法区的大小来解决, 使用类似 - XX:PermSize=64m -XX:MaxPermSize=256m 的形式修改。注意, 过多的常量尤其是字符串也会导致方法区溢出。
  • java.lang.StackOverflowError ------> 不会抛 OOM error, 但也是比较常见的 Java 内存溢出。JAVA 虚拟机栈溢出, 一般是由于程序中存在死循环或者深度递归调用造成的, 栈大小设置太小也会出现此种溢出。可以通过虚拟机参数 - Xss 来设置栈的大小。

三、问题排查

3.1确认是不是内存本身就分配过小

方法:jmap -heap PID

可以查看JVM新生代,老生代堆内存的分配大小以及使用情况,看是否本身分配过小。预估系统线程数与实际使用情况。

3.2找到最耗内存的对象

方法:jmap -histo:live PID | more

输入命令后,会以表格的形式显示存活对象的信息,并按照所占内存大小排序:实例数、所占内存大小、类名

若先生环境可以dump下了本地使用工具查看

3.3资源耗尽情况

查看进程创建的线程数,以及网络连接数,如果资源耗尽,也可能出现OOM。

可通过

  • /proc/${PID}/fd
  • /proc/${PID}/task

可以分别查看句柄详情和线程数。对于此类调用可以加上超时时间等。

四、OOM 常见原因及解决方案

4.1 Java heap space

堆内存 (Heap Space) 没有足够空间创建新创建的对象。

常见原因可分为分析

  • 请求创建一个超大对象,通常是一个大数组。
  • 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
  • 过度使用(Finalizer),该对象没有立即被 GC。
  • 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。

解决方案,针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:

  • 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
  • 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
  • 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。
  • 对于外部网络请求,可以设置超时时间,请求回调改造

4.2 PermGen space

该错误表示永久代 (Permanent Generation) 已用满, 通常是因为加载的 class 数目太多或体积太大。

原因分析,永久代存储对象主要包括以下几类:

  • 加载/缓存到内存中的 class 定义,包括类的名称,字段,方法和字节码;
  • 常量池;
  • 对象数组/类型数组所关联的 class;
  • JIT 编译器优化后的 class 信息。
  • PermGen 的使用量与加载到内存的 class 的数量/大小正相关。

解决方案,根据 Permgen space 报错的时机,可以采用不同的解决方案,如下所示:

  • 程序启动报错,修改 -XX:MaxPermSize 启动参数,调大永久代空间。
  • 应用重新部署时报错,很可能是没有应用没有重启,导致加载了多份 class 信息,只需重启 JVM 即可解决。
  • 运行时报错,应用程序可能会动态创建大量 class,而这些 class 的生命周期很短暂,但是 JVM 默认不会卸载 class,可以设置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC这两个参数允许 JVM 卸载 class。
  • 如果上述方法无法解决,可以通过 jmap 命令 dump 内存对象 jmap-dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT Eclipse Memory Analyzer Open Source Project | The Eclipse Foundation 功能逐一分析开销最大的 classloader 和重复 class。

4.3 GC overhead limit exceeded

当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误。简单地说,就是应用程序已经基本耗尽了所有可用内存, GC 也无法回收。

此类问题的原因与解决方案跟 Javaheap space 非常类似,可以参考上文。

4.4 Metaspace

JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。

此类问题的原因与解决方法跟 Permgenspace 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize。


4.5 Unable to create new native thread

每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误。

原因分析,JVM 向 OS 请求创建 native 线程失败,就会抛出 Unableto createnewnativethread,常见的原因包括以下几类:

  • 线程数超过操作系统最大线程数 ulimit 限制;
  • 线程数超过 kernel.pid_max(只能重启);
  • native 内存不足;

该问题发生的常见过程主要包括以下几步:

  • JVM 内部的应用程序请求创建一个新的 Java 线程;
  • JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程;
  • 操作系统尝试创建一个新的 native 线程,并为其分配内存;
  • 如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配;
  • JVM 将抛出 java.lang.OutOfMemoryError:Unableto createnewnativethread 错误。

解决方案

  • 升级配置,为机器提供更多的内存;
  • 降低 Java Heap Space 大小;
  • 修复应用程序的线程泄漏问题;
  • 限制线程池大小;
  • 使用 -Xss 参数减少线程栈的大小;
  • 调高 OS 层面的线程最大数:执行 ulimia-a 查看最大线程数限制,使用 ulimit-u xxx 调整最大线程数限制ulimit -a … 省略部分内容 … max user processes (-u) 16384

4.6 Out of swap space?

该错误表示所有可用的虚拟内存已被耗尽。虚拟内存(Virtual Memory)由物理内存(Physical Memory)和交换空间(Swap Space)两部分组成。当运行时程序请求的虚拟内存溢出时就会报 Outof swap space? 错误。

原因分析,该错误出现的常见原因包括以下几类:

  • 地址空间不足;
  • 物理内存已耗光;
  • 应用程序的本地内存泄漏(native leak),例如不断申请本地内存,却不释放。
  • 执行 jmap-histo:live 命令,强制执行 Full GC;如果几次执行后内存明显下降,则基本确认为 Direct ByteBuffer 问题。

解决方案,根据错误原因可以采取如下解决方案:

  • 升级地址空间为 64 bit;
  • 使用 Arthas 检查是否为 Inflater/Deflater 解压缩问题,如果是,则显式调用 end 方法。
  • Direct ByteBuffer 问题可以通过启动参数 -XX:MaxDirectMemorySize 调低阈值。
  • 升级服务器配置/隔离部署,避免争用。

4.7 Kill process or sacrifice child

有一种内核作业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer 会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考 Surviving the Linux OOM Killer。不同于其他的 OOM 错误, Killprocessorsacrifice child 错误不是由 JVM 层面触发的,而是由操作系统层面触发的。

原因分析

默认情况下,Linux 内核允许进程申请的内存总量大于系统可用内存,通过这种“错峰复用”的方式可以更有效的利用系统资源。

然而,这种方式也会无可避免地带来一定的“超卖”风险。例如某些进程持续占用系统内存,然后导致其他进程没有可用内存。此时,系统将自动激活 OOM Killer,寻找评分低的进程,并将其“杀死”,释放内存资源。

解决方案

  • 升级服务器配置/隔离部署,避免争用。
  • OOM Killer 调优。

4.8 Requested array size exceeds VM limit

JVM 限制了数组的最大长度,该错误表示程序请求创建的数组超过最大长度限制。

JVM 在为数组分配内存前,会检查要分配的数据结构在系统中是否可寻址,通常为 Integer.MAX_VALUE-2。

此类问题比较罕见,通常需要检查代码,确认业务是否需要创建如此大的数组,是否可以拆分为多个块,分批执行。

4.9、Direct buffer memory

java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory Mapped File)实现高速 IO。

原因分析

  • Direct ByteBuffer 的默认大小为 64 MB,一旦使用超出限制,就会抛出 Directbuffer memory 错误。

解决方案

  • Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查。
  • 检查是否直接或间接使用了 NIO,如 netty,jetty 等。
  • 通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值。
  • 检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项,如果有就去掉,因为该参数会使 System.gc() 失效。
  • 检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间。
  • 内存容量确实不足,升级配置。

标签:ByteBuffer,OOM,解决方案,线程,内存,JVM,class,原因
From: https://blog.51cto.com/u_12266412/8980496

相关文章

  • ChatGPT变懒原因:正在给自己放寒假!已被网友测出
    ChatGPT近期偷懒严重,有了一种听起来很离谱的解释:模仿人类,自己给自己放寒假了~添加图片注释,不超过140字(可选)有测试为证,网友@RobLynch用GTP-4turboAPI设置了两个系统提示:一个告诉它现在是5月,另一个告诉它现在是12月。然后使用完全相同的提示词要求GTP-4“完成一个机器学习相关的......
  • 收不到异步通知的原因
    之前有和大家分享过支付宝异步通知如何使用 的相关内容,但是有些时候吧,就是收不到异步通知,也不知道是什么原因导致的。今天来帮大家汇总下在「收不到异步通知」的情况下,如何排查问题出现在哪里。  异步通知发送的条件在文章[手把手|支付宝异步通知如何使用]中有详细介绍......
  • VS2019,无法启动程序xxx.exe,系统找不到指定的文件,重新生成解决方案报错
     调试程序报错如图一、尝试重新生成解决方案二、如果生成解决方案也报错,重新安装.netSDK本人所用为VS2019,.net5,到官网下载.net5的SDK重新安装后,恢复正常,重新生成成功,启动调试成功。.net各版本下载地址:https://dotnet.microsoft.com/en-us/download/dotnet.net5下载地址:h......
  • 【JVM调优】内存溢出+CPU占用过高:问题排查+解决方案+复盘
    前言最近刚上线了一款社交项目,运行十多天后(运营持续每天推量),发现问题:系统OOM(资源不能被释放)导致服务器频繁且长时间FGC导致服务器CPU持续飚高日志中内存溢出:java.lang.OutOfMemoryError:Javaheapspace程序十分卡顿,严重影响用户使用从以下方面,为大家分享此次问题解决流程问题出......
  • 企业实战总结:SQL Join执行的常见问题及解决方案
    1.背景SQL的join对于数据开发同学是最经常遇到的操作,通过表与表之间的关联来得到想要的数据。但是在开发中我们会遇到一些莫名奇妙的问题,本文就选择最常见的两类问题跟大家分享。2.结果不符合预期2.1string和bigint做join,出现重复数据这个问题源于底层的隐式转换规则,当string和b......
  • 云服务器比传统服务器更安全的原因与实现机制
    本文分享自天翼云开发者社区《云服务器比传统服务器更安全的原因与实现机制》,作者:3****m随着互联网的普及和云计算技术的发展,越来越多的企业和组织选择使用云服务器来提供和存储数据。与传统服务器相比,云服务器在安全性方面具有诸多优势。本文将围绕云服务器比传统服务器更安全的......
  • EasyCVR点击通道后页面分页不显示是什么原因?如何解决?
    有用户反馈,EasyCVR在点击当前通道列表分页后,再点击其他设备时,页面不加载任何通道,如下对比:经过技术人员排查后发现,原因是重新点击设备时,带了之前的分页数据,才导致页面没有数据;查看代码发现,设备编码变更时,没有重置分页参数;于是新增重置分页参数代码,即可解决该问题。......
  • Hive“横空出世”的原因
         在沸沸扬扬的大数据江湖里,有这么一个框架,它刚开始很低调,然而,就在那么一个不起眼的一天,突然展示出其绝妙的武功,让大数据各大框架的掌门人暗挑大拇指,好了,不卖关子了,这个“横空出世”的框架就是——Hive。    一:Hive产生的背景    万事有因果,Hive的出......
  • 智能监控平台/视频共享融合系统EasyCVR点击通道后页面分页不显示是什么原因?如何解决?
    TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力,也具备接入AI智能分析的能力,包括对人、车、......
  • GB28181视频监控平台LiteCVR调用rtsp地址返回的IP不正确原因排查
    RTSP(Real-TimeStreamingProtocol)是一种用于控制实时流媒体传输的应用层协议。它被设计用于建立和管理客户端与媒体服务器之间的连接,以便实现实时音频、视频或其他交互式媒体内容的传输。RTSP允许客户端通过发送命令来控制流媒体服务器的播放、暂停、快进、倒带等操作。RTSP支持......