OOM三大场景和解决方案

目录

首先,说说什么是OOM?

Java OOM的三大核心场景

场景一、堆内存OOM

类型一:在线OOM分析,这个属于轻量级的分析:

类型二:离线OOM分析,这个属于轻量级的分析:

场景二:元空间(MetaSpace) OOM

什么是元空间(MetaSpace)

元空间(Metaspace) OOM现象

元空间(Metaspace) OOM 原因

如何定位和解决

元空间(Metaspace) OOM 解决办法:

场景三:堆外内存 OOM

堆外内存 OOM 现象

堆外内存 OOM 原因

堆外内存解决OOM对策


首先,说说什么是OOM?

OOM 全称 “Out Of Memory”,表示内存耗尽。

官方说明:Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector.

当 JVM 因为没有足够的内存来为对象分配空间,并且垃圾回收器也已经没有空间可供回收时,就会抛出这个错误。(注:非exception,已经严重到不足以被应用处理)。

为什么会出现 OOM,一般由这些问题引起

  1. 分配过少:JVM 初始化内存小,业务使用了大量内存;或者不同 JVM 区域分配内存不合理

  2. 内存泄漏:某一个对象被频繁申请,不用了之后却没有被释放,发生内存泄漏,导致内存耗尽

内存泄漏:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了。因为申请者不用了,而又不能被虚拟机分配给别人用

内存溢出:申请的内存超出了 JVM 能提供的内存大小,此时称之为溢出

内存泄漏持续存在,最后一定会溢出,两者是因果关系

Java OOM的三大核心场景

图片

场景一、堆内存OOM

OOM的场景和解决方案

图片

分析方法通常有两种:

  • 类型一:在线分析,这个属于轻量级的分析:

  • 类型二:离线分析,这个属于重量级的分析:

类型一:在线OOM分析,这个属于轻量级的分析:

在线OOM分析,包括两种方法:

在线分析方法一:使用 jmap 分析TOP N对象

jmap(Java Memory Map)是jdk自带的java内存映像工具,使用jmap能够系统运行时的内存信息,同时能够将内存dump下来,分析内存泄露的问题。

  • 第一步:jmap 查看进程中占用资源最大的前N个对象,

  • 第二步:知道哪个对象消耗内存了,再去定位代码就不难了。然后 导出 快照文件 jmap -dump:live,format=b,file=文件路径/文件名 pid

这里我们使用它 -dump 选项,将内存信息dump到服务器某个地方,然后传到本地使用内存分析工具MAT进行内存分析。

jmap -dump:live,format=b,file=文件路径/文件名 pid

live:就是只dump 活着的对象 format=b 使用二进制 file= 快照文件保存路径


在线分析方法二:使用 Arthas 在线分析OOM

使用 Arthas 火焰图,分析TOP N对象 和调用堆栈

请参见视频, 和尼恩的《Arthas 学习圣经 v2》 最新版本

类型二:离线OOM分析,这个属于轻量级的分析:

第一步:使用Java内存快照工具:jmap 生成堆转储快照(一般称为headdump或dump文件)。

或者从服务器copy OOM自动dump出来的dump文件。

下面来一份JDK8的JVM参数默认配置

-Xms2g -Xmx2g (按不同容器,4G及以下建议为50%,6G以上,建议设置为70%)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=512m
-Xss256k
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:AutoBoxCacheMax=20000
-XX:+HeapDumpOnOutOfMemoryError (当JVM发生OOM时,自动生成DUMP文件)
-XX:HeapDumpPath=/usr/local/logs/gc/
-XX:ErrorFile=/usr/local/logs/gc/hs_err_%p.log (当JVM发生崩溃时,自动生成错误日志)
-XX:+PrintGCApplicationStoppedTime
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/usr/local/heap-dump/
  • -XX:+HeapDumpOnOutOfMemoryError

    从字面就可以很容易的理解,在发生OutOfMemoryError异常时,进行堆的Dump,这样就可以获取异常时的内存快照了。

  • -XX:HeapDumpPath=/usr/local/heap-dump/

这个也很好理解,就是配置HeapDump的路径,

方便我们管理,这里我们配置为/usr/local/heap-dump/,当然你也可以根据自己的需要,定义为其他的目录。

JVM相关的启动参数 给出一些实战经验,让工作中更加从容:

  1. 调优参数务必加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=,发生OOM让JVM自动dump出内存,方便后续分析问题解决问题

  2. 堆内存不要设置的特别大,因为你设置的特别大,发生OOM时生成的dump文件就特别大,不好分析。建议不超过8G。

  3. 想主动dump出JVM内存,有挺多方式,但不管哪种方式,主动dump内存会引发STW,请线上压力最小的时间段操作。

    即通过arthas提供的命令heapdump主动dump出JVM的内存,这个操作会引发FGC,背后是STW,操作时请选择好时机,不然老板可能提刀来见。

第2步:导入到jvisualvm进行分析

场景二:元空间(MetaSpace) OOM

什么是元空间(MetaSpace)

JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace);这与Oracle JRockit 和IBM JVM’s很相似,如下图所示

图片

这意味着不会再有java.lang.OutOfMemoryError: PermGen 问题,也不再需要你进行调优及监控内存空间的使用……

但请等等,这么说还为时过早。

在默认情况下,这些改变是透明的,接下来我们的展示将使你知道仍然要关注类元数据内存的占用。

请一定要牢记,元空间在直接内存,但是没有  消除类和类加载器导致的内存泄漏

由于永久代PermGen 空间被移除。所以,JVM 8的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(如果在启用时设置了这两个参数)。

元空间是方法区在HotSpot JVM 中的实现,方法区主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。

元空间的本质和永久代类似,都是对JVM规范中方法区的实现。

不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小,可见也不是无限制的,需要配置参数。

元空间(Metaspace) 垃圾回收,会对僵死的类及类加载器的垃圾回收会进行回收,元空间(Metaspace) 垃圾回收的时机是,在元数据使用达到“MaxMetaspaceSize”参数的设定值时进行。

元空间(Metaspace) 容量

默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)。

一般情况下避免 MetaSpace 耗尽内存,都会设置一个 MaxMetaSpaceSize参数,MaxMetaspaceSize用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。

动态的调整会造成元空间数据的复制和GC的消耗,为了避免弹性伸缩带来的额外 GC 消耗,我们会将-XX:MetaSpaceSize和-XX:MaxMetaSpaceSize两个值设置为固定的,但是这样也会导致在空间不够的时候无法扩容,然后频繁地触发 GC,最终 OOM。

在运行过程中,如果实际大小小于这个值,JVM 就会通过 -XX:MinMetaspaceFreeRatio 和 -XX:MaxMetaspaceFreeRatio 两个参数动态控制整个 MetaSpace 的大小。监控和调整元空间对于减小垃圾回收频率和减少延时是很有必要的。

持续的元空间垃圾回收说明,可能存在类、类加载器导致的内存泄漏或是大小设置不合适。

元空间(Metaspace) OOM现象

JVM 在启动后或者某个时间点开始,MetaSpace 的已使用大小在持续增长,同时每次 GC 也无法释放,调大 MetaSpace 空间也无法彻底解决

元空间(Metaspace) OOM 原因

核心原因:生成大量动态类

比如spring的BeanUtils的拷贝对象,json的序列化大量使用反射,

而反射在大量使用时,因为使用了缓存的原因,导致ClassLoader和它引用的Class等对象不能被回收,

反射(包括上面提到的spring的BeanUtils的拷贝对象,json的序列化),而反射在大量使用时,因为使用了缓存的原因,导致ClassLoader和它引用的Class等对象不能被回收,

如何定位和解决

分析dump文件,一般会在日志中发现了“Metaspace OOM”的提示

元空间(Metaspace) OOM 解决办法:

  • 减少代码中,使用反射的情况,或者对反射进行优化。

  • 测试出服务实例的能力上限,进行服务的过载保护比如(限流等),防止突发流量将服务

场景三:堆外内存 OOM

堆外内存 OOM 现象

  • 现象1:Java 进程的 RES 甚至超过了 -Xmx 的大小

  • 现象2:Java 进程假死

Java 进程的 RES 甚至超过了 -Xmx 的大小 怎么看呢?通过 top 命令发现 Java 进程的 RES 甚至超过了** -Xmx 的大小。出现这些现象时,基本可以确定是出现了堆外内存泄漏。

使用top命令查看内存和cpu占用高的java进程,使用下面的命令:

top -c -p $(pgrep -d',' -f java)

图片

top命令查看进程信息, 主要的字段含义如下:

  • PID:进程的标识符。

  • USER:运行进程的用户名。

  • PR(优先级):进程的优先级。

  • NI(Nice值):进程的优先级调整值。

  • VIRT(虚拟内存):进程使用的虚拟内存大小。

  • RES(常驻内存):进程实际使用的物理内存大小。

  • SHR(共享内存):进程共享的内存大小。

  • %CPU:进程占用 CPU 的使用率。

  • %MEM:进程占用内存的使用率。

  • TIME+:进程的累计 CPU 时间。

top命令的res表示实际占用的内存,RES(Resident Set Size)是用来表示进程占用的物理内存的指标之一,它的单位是KB(千字节)。

具体地说,RES是指当前进程正在使用的物理内存大小,它包括了进程自身和它所拥有的子进程使用的内存,但不包括被共享的内存和被交换到磁盘上的内存。

res可能比xmx设置的要大, 因为统计内容不同

  • xmx只是堆内存(包括新生代(eden,from,to),老年代),

  • res范围更广,还包括metaDate,堆外内存等,

堆外内存 OOM 原因

JVM 的堆外内存泄漏,主要有两种的原因:

  • 通过 UnSafe#allocateMemory,ByteBuffer#allocateDirect 主动申请了堆外内存而没有释放,常见于 NIO、Netty 等相关组件。

  • 代码中有通过 JNI 调用 Native Code 申请的内存没有释放。

堆外内存解决OOM对策

  • 进行线上指标监控

    通过反射获取堆外内存的指标,并且通过在线Prometheus+grafana进行采集和 监控,如果堆外内存一直增长,就大概率泄漏

  • 内存泄漏检测进行检测,然后根据找到泄漏的内存,进行Netty引用计数的清零

    一般泄漏都发生在最后一次使用后忘记调用释放方法造成

    通过Netty自带内存泄漏检测工具,配合压力测试,进行内存泄露检测, 解决OOM之后再上线。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值