发生OOM了,你知道是什么原因吗,又该怎么解决呢?

简介: 总结常见的OOM的原因以及解决方案。

前言

撸java代码的同学,多多少少都会碰到内存溢出(OOM)的场景,但是造成OOM原因却不止一个。今天就来总结一下常见的OOM的原因以及解决方案。

1、堆内存不足(Java heap space)

原因:

  1. 代码中可能存在大对象分配,通常是一个大树组。
  2. 可能存在内存泄露,导致多次GC之后,还是无法找到一块足够大的内存容纳当前对象,常见于使用了File等资源没有回收。
  3. 超出预期的访问量、数据量,通常是上游系统请求流量飙升,常见于各类促销、秒杀活动,可以结合业务流量指标排查是否有尖状峰值。

解决方案:

  1. 针对大部分的情况,通常只需要通过-Xmx参数调高JVM堆内存即可。如果仍然没有解决,可以参考下面几种情况进行处理。
  2. 如果是超大对象,检查其合理性,比如是否一次查询了数据库全部结果,而没有做结果数限制。
  3. 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级操作。
  4. 如果是内存泄露,需要找到持有对象,修改代码设计,比如关闭没有释放的连接。

2、永久代空间/元空间(Permgen space/ metaspace)

原因:

  1. 永久代是HotSot虚拟机对方法区的具体实现,存放被虚拟机加载的类信息、常量池、静态变量,JIT编译后的代码等
  2. Perngen的使用量和加载到内存的class的数量/大小正相关

解决方案:

  1. 检查是否永久代空间(启动参数:-XX:MaxPermSize)或者元空间(启动参数:-XX:MaxMetaspaceSize)设置的过小。
  2. 检查代码中是否存在大量反射操作
  3. dump之后通过mat检查是否存在大量由于反射生成的代理类
  4. 应用部署时报错,很可能没有重启应用,导致加载了多份class信息,重启JVM即可解决

3、GC overhead limit exceeded

原因:

  1. 当java进程花费98%以上时间来做GC并且回收了不到2%的堆内存时会抛出此异常。
  2. 堆内存太小

解决方案:

  1. 检查项目中是否有大量的死循环或者使用大内存的代码,优化代码
  2. dump内存,检查是否存在内存泄露,如果没有,加大内存。

4、方法栈溢出(Unable to create new native thread)

原因:

  1. 线程数超过操作系统最大线程数ulimit限制
  2. 线程数超过kernal.pid_max
  3. native 内存不足

解决方案:

  1. 升级配置,为机器提供更多内存
  2. 降低Java heap space大小
  3. 修复应用程序的线程泄露问题
  4. 限制线程池大小
  5. 使用-Xss参数减少线程栈大小
  6. 调高OS层面的线程最大数:执行ulimit -a 查看最大线程数限制,使用ulimit -u xx 调整最大线程数限制

5、swap区溢出(Out of swap space)

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

原因:

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

解决方案:

  1. 升级地址空间为64bit
  2. 使用Arthas检查是否为Inflater/Deflater 解压问题,如果是,则显示调用end方法
  3. Direct ByteBuffer问题可以通过启动参数–XX:MaxDirectMemorySize降低阈值
  4. 加大swap分区大小或者加大机器内存大小
  5. 隔离部署,避免争抢

6、分配超大数组(Requested array size exceeds VM limit)

原因:

这种情况一般是由于不合理数组分配请求导致的,在为数组分配内存之前,JVM会执行一项检查。要分配的数组在该平台是否可以寻址,如果不能寻址就会抛出这个错误。

解决方案:

检查代码中是否有创建超大数组的地方

7、Direct buffer memory

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

原因:

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

解决方案:

Java只能通过ByteBuffer.allocateDirect方法使用DirectByteBuffer,因此,可以通过Arthas等在线诊断工具拦截该方法进行排查。

  1. 检查是否直接或间接使用了NIO,如netty,jetty等
  2. 通过启动参数-XX:MaxDirectMemorySize调整Direct ByteBuffer上限值
  3. 检查JVM参数是否有-XX:+DisableExplicitGC选项,如果有去掉,因为该参数会使System.gc()失效
  4. 检查堆外内存的代码,确认是否存在内存泄露;或者通过反射调用sun.misc.Cleaner的clean() 方法来主动释放Direct ByteBuffer持有的内存空间。
  5. 内存容量确实不够的,升级配置

8、Kill process or sacrifice child

有一种内核作业(Kernel Job)名为 Out of Memory Killer,它会在可用内存极低的情况下“杀死”(kill)某些进程。OOM Killer 会对所有进程进行打分,然后将评分较低的进程“杀死”,具体的评分规则可以参考 Surviving the Linux OOM Killer。

不同于其他的 OOM 错误,Kill processorsacrifice child错误不是由 JVM 层面触发的,而是由操作系统层面触发的。

原因:

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

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

解决方案:

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

结语

好了,今天就为大家分享到这里了。如果本文对你有帮助的话,欢迎点赞&收藏&分享,这对我继续分享&创作优质文章非常重要。感谢🙏🏻

相关文章
|
缓存 Java Android开发
【OOM异常排查经验】
【OOM异常排查经验】
213 0
|
4月前
|
缓存 算法 Java
JVM内存溢出(OutOfMemory)异常排查与解决方法
JVM内存溢出(OutOfMemory)异常排查与解决方法
|
6月前
|
监控 网络协议 NoSQL
java线上排查OOM内存溢出
java线上排查OOM内存溢出
240 1
|
6月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
|
6月前
|
缓存 架构师 算法
Java内存溢出如何解决,Java oom排查方法,10个定位解决办法
在Java开发过程中,有效的内存管理是保证应用程序稳定性和性能的关键。不正确的内存使用可能导致内存泄露甚至是致命的OutOfMemoryError(OOM)。
139 0
|
消息中间件 存储 缓存
【10个OOM异常的场景以及对应的排查经验】
【10个OOM异常的场景以及对应的排查经验】
207 0
|
存储 缓存 监控
37-大厂面试题:什么是内存溢出?在哪些区域会发生内存溢出?请解释下直接内存OOM
什么是内存溢出?在哪些区域会发生内存溢出?请解释下直接内存OOM
143 0
|
Java
OOM排查小案例
写作目的 排查过某OOM问题吗?额。。。没有
198 0
OOM排查小案例
|
监控 Java 数据安全/隐私保护
JVM频繁GC内存溢出排查
GC(Garbage collection)频繁和堆内存溢出原因简单来说是对象占用堆空间难以回收,新对象无法分配触发GC或者直接导致内存溢出,最终进程结束。
465 0
|
Dubbo Java 应用服务中间件
线上报了内存溢出异常,又不完全是内存溢出
最近一直忙于对付即将上线的系统,期间也碰到了很多问题。最近印象比较深的是一个内存溢出的报错。测试告诉我最近某个功能总是没有效果,于是我就去线上看了一下错误日志,这不看不知道,一看吓一跳,满屏的OutOfMemoryError ,出于隐私保护,这里只展示其中的一点异常信息: