OOM问题如何解决

概述

oom 以及遇到这种情况怎么处理的,是否使用过日志分析工具 OOM,全称“Out Of Memory”,
翻译成中文就是“内存用完了”,当 JVM 因为没有足够的 内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error。 处理过程:首先通过内存映射分析工具 如 Eclipse Memory Analyzer 堆 dump 出的异常 堆转储进行快照解析确认内存中的对象是否是必要的, 也就是先分清楚是 内存泄漏 Memory Leak 还是 Memory Overflow 如果是内存泄漏可通过工具进一步查看泄露的对象到 GC Roots 的引用链, 就能找到泄露对象是怎么通过路径与 GC Roots 相关联导致垃圾收集器无法回收他们如 果不存在泄露 就检查堆参数 -Xmx 与 -Xms 与机器物理 内存对比是否还可以调大 从代码上检测 是否是某些对象的生命周期过长持有状态时间 过长 尝试减少代码运行期间的内存消耗。
在这里插入图片描述

HeapDumpOnOutOfMemoryError 是一个 JVM 参数,用于在程序发生 OutOfMemoryError(内存溢出)错误时生成堆转储文件(Heap Dump),以便进行后续的分析和调试。
当应用程序发生 OutOfMemoryError 错误时,通常会导致应用程序崩溃或出现不可预测的行为。为了帮助诊断问题并了解内存使用情况,可以通过设置 HeapDumpOnOutOfMemoryError 参数来生成 Java 堆的转储文件。该文件包含了当前应用程序的内存快照,包括对象的组织结构、大小和引用关系等信息。
在命令行启动 Java 程序时,可以使用以下参数来开启 HeapDumpOnOutOfMemoryError:
-XX:+HeapDumpOnOutOfMemoryError
默认情况下,生成的堆转储文件将保存在当前工作目录下。可以使用 -XX:HeapDumpPath 参数来指定堆转储文件的路径:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump/file.hprof
生成的堆转储文件可以使用各种 Java 堆转储分析工具进行分析,如 Eclipse MAT(Memory Analyzer Tool)或 VisualVM(可视化监视和分析工具)等。
请注意,在生产环境中开启 HeapDumpOnOutOfMemoryError 可能会对性能产生一定影响,因此建议仅在调试和分析阶段使用该参数。

怎么排查 OOM 的问题

增加两个参数 -XX:+HeapDumpOnOutOfMemoryError -
XX:HeapDumpPath=/tmp/heapdump.hprof,当 OOM 发生时自动 dump 堆内存信
息到指定目录。
同时 jstat 查看监控 JVM 的内存和 GC 情况,先观察问题大概出在什么区域。
使用 MAT 工具载入到 dump 文件,分析大对象的占用情况,比如 HashMap 做缓存未
清理,时间长了就会内存溢出,可以把改为弱引用。

配置OOM时候的内存dump文件和GC日志

增加GC日志打印、OOM自动dump等配置内容,帮助进行问题排查
-XX:+HeapDumpOnOutOfMemoryError
在Out Of Memory,JVM快死掉的时候,输出Heap Dump到指定文件。
不然开发很多时候还真不知道怎么重现错误。
路径只指向目录,JVM会保持文件名的唯一性,叫java_pid p i d . h p r o f 。 − X X : + H e a p D u m p O n O u t O f M e m o r y E r r o r − X X : H e a p D u m p P a t h = {pid}.hprof。 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath= pid.hprofXX:+HeapDumpOnOutOfMemoryErrorXX:HeapDumpPath={LOGDIR}/
因为如果指向特定的文件,而文件已存在,反而不能写入。
输出4G的HeapDump,会导致IO性能问题,在普通硬盘上,会造成20秒以上的硬盘IO跑满,
需要注意一下,但在容器环境下,这个也会影响同一宿主机上的其他容器。
GC的日志的输出也很重要:
-Xloggc:/dev/xxx/gc.log
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
GC的日志实际上对系统性能影响不大,打日志对排查GC问题很重要。

通过jvisualvm定位OOM问题

获取dump文件
注意导出文件占用内存很大的时候,可能会导不出来
方式一:提前配置jvm参数,在系统挂了后会在指定目录下生成dump文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./ (导出路径)
方式二:使用命令行导出
jmap -dump:format=b,file=demo.hprof pid
通过jvisualvm工具装载dump文件
查看内存占用最高的业务对象,并找到GCRoot并查看线程栈从而定位问题
在这里插入图片描述

在这里插入图片描述

通过Arthas工具定位问题

执行如下命令下载arthas-boot.jar,再用java -jar命令启动:
wgethttps://arthas.aliyun.com/arthas-boot.jar; java-jararthas-boot.jar
arthas-boot是Arthas的启动程序,它启动后,会列出所有的Java进程,用户可以选择需要诊断的目标进程。
使用dashboard 命令可以查看当前系统的实时数据面板。可以查看到CPU、内存、GC、运行环境等信息。
使用 sc 命令来查找JVM里已加载的类,通过-d参数,可以打印出类加载的具体信息,很方便查找类加载问题。

JVM 发生OOM情况

OOM即out of memory,内存溢出,与JVM的运行时内存有关,当JVM内存不够时就会发生OOM,主要分一下几种情况:
1.堆溢出:堆是JVM存放对象实例的地方,如果我们产生的对象过多,JVM又没有及时的GC,就会突破最大堆容量限制从而发生OOM
2.操作栈或本地方法栈溢出:如果线程在拓展栈时无法申请到足够的内存,也会发生OOM
3.方法区溢出:方法区存储了JVM中的常量,静态变量和类信息等信息,一个类如果要被垃圾收集器回收,判定条件是很苛刻的,因此在经常动态生成大量Class也可能发生OOM
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常,方法递归调用产生这种结果
1、Java堆溢出:heap
Java堆内存主要用来存放运行过程中所以的对象,该区域OOM异常一般会有如下错误信息;
java.lang.OutofMemoryError:javaheap space
此类错误一般通过Eclipse Memory Analyzer分析OOM时dump的内存快照就能分析出来,到底是由于程序原因导致的内存泄露,还是由于没有估计好JVM内存的大小而导致的内存溢出。
另外,Java堆常用的JVM参数:
-Xms:初始堆大小,默认值为物理内存的1/64(<1GB),默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制
-Xmx:最大堆大小,默认值为物理内存的1/4(<1GB),默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制
-Xmn:年轻代大小(1.4or lator),此处的大小是(eden + 2 survivor space),与jmap -heap中显示的New gen是不同的。
当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.OutOfMemoryError:Javaheap space 错误(根据实际生产经验,可以对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,立即处理)。
堆内存原因
Javaheap space 错误产生的常见原因可以分为以下几类:
1、请求创建一个超大对象,通常是一个大数组。
2、超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
3、过度使用终结器(Finalizer),该对象没有立即被 GC。
4、内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。
解决方案
针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:
1、如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
2、如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
3、如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。

2、栈溢出:stack
栈用来存储线程的局部变量表、操作数栈、动态链接、方法出口等信息。如果请求栈的深度不足时抛出的错误会包含类似下面的信息:
java.lang.StackOverflowError
另外,由于每个线程占的内存大概为1M,因此线程的创建也需要内存空间。操作系统可用内存-Xmx-MaxPermSize即是栈可用的内存,如果申请创建的线程比较多超过剩余内存的时候,也会抛出如下类似错误:
java.lang.OutofMemoryError: unable to create new native thread
相关的JVM参数有:
-Xss: 每个线程的堆栈大小,JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K.
在相同物理内存下,减小这个值能生成更多的线程.但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

3、运行时常量溢出 constant
运行时常量保存在方法区,存放的主要是编译器生成的各种字面量和符号引用,但是运行期间也可能将新的常量放入池中,比如String类的intern方法。
如果该区域OOM,错误结果会包含类似下面的信息:
java.lang.OutofMemoryError: PermGen space
相关的JVM参数有:
-XX:PermSize:设置持久代(perm gen)初始值,默认值为物理内存的1/64
-XX:MaxPermSize:设置持久代最大值,默认为物理内存的1/4

4、方法区溢出 directMemory
方法区主要存储被虚拟机加载的类信息,如类名、访问修饰符、常量池、字段描述、方法描述等。理论上在JVM启动后该区域大小应该比较稳定,但是目前很多框架,比如Spring和Hibernate等在运行过程中都会动态生成类,因此也存在OOM的风险。
如果该区域OOM,错误结果会包含类似下面的信息:
java.lang.OutofMemoryError: PermGen space
相关的JVM参数可以参考运行时常量。
另外,在定位JVM内存问题的时候可以借助于一些辅助信息:
1、日志相关
-XX:+PrintGC:输出形式:
[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
-XX:+PrintGCDetails:输出形式:
[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
-XX:+PrintGCTimeStamps:打印GC停顿耗时
-XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间.
-XX:+PrintHeapAtGC:打印GC前后的详细堆栈信息
-Xloggc:filename:把相关日志信息记录到文件以便分析

2、错误调试相关:
-XX:ErrorFile=./hs_err_pid.log:如果JVM crashed,将错误日志输出到指定文件路径。
-XX:HeapDumpPath=./java_pid.hprof:堆内存快照的存储文件路径。
-XX:-HeapDumpOnOutOfMemoryError:在OOM时,输出一个dump.core文件,记录当时的堆内存快照
3、类装载相关
-XX:-TraceClassLoading:打印class装载信息到stdout。记Loaded状态。
-XX:-TraceClassUnloading:打印class的卸载信息到stdout。记Unloaded状态。

oom发生的场景

1.文件导入导出有关
2.查询数据量过大
解决方案:
批量分页多线程查询

排查案例1

2000路压测时api出现oom报错,使用dump命令,生成hprof文件,然后使用MAT工具分析。
经过分析后,Dominator Tree 中发现排名前2的对象占了内存的80%,为定时任务查询数据库对象和mysql的数据库对象。具体哪个定时任务造成的无法确定
检查定任务代码后发现问题。
具体修复问题如下:
dunm命令
jmap -dump:format=b,file=dump.hprof 6

OOM时的排查过程2

多途径获取出现问题的进程

  1. 监控系统报警
  2. 应用异常,报错
  3. 日常巡检发现了OOM之后的堆内存快照
  4. 日常巡检发现cpu居高不下 当然无论你是从什么途径发现异常的,你都会定位到出现问题的应用,并且看到如下的日志。
    在这里插入图片描述

没错他真的OOM了。
获取堆快照
你的java应用在启动时设置如下JVM参数就可以在OOM时自动产生堆内存快照了。
在这里插入图片描述

log/dump/下面就会出现你想要的对内存快照文件。
如果你运气不错的话虽oom了但是你的应用还没挂,那么你可以使用下面的方法获得一份内存快照。
● 查看应用的PID

可以使用ps命令查看应用的PID,当然你一可以top -c 排名第一的应用大概率还是你那个oom的应用。
● jmap创建堆内存快照
23973就是上面的PID
在这里插入图片描述

到此为止如果你的程序已经挂了你直接跳到最后看如何离线分析堆快照吧。
如果你的程序还没挂,你还可以执行下面的命令查看一下jvm的信息,当然如下的方法也可以是日常巡检,人工也好脚本也罢的巡检内容。
查看堆信息

可以看到除了新生代的to区包括老生代在内的区域都已经99%了。
查看堆栈信息
在这里插入图片描述

关于如何堆栈信息的查看,我之前写过的一篇线上cpu使用率100%如何排查里面有更详细的jstack使用方法。
查看GC信息
在这里插入图片描述

可以看到进行了2195此Full GC,并且从后面的时间看几乎全部的时间都在Full gc。
离线分析堆内存快照
这里我采用的是JDK自带的jvisualvm进行分析。
找到你的JAVA_HOME打开它
在这里插入图片描述

当然你也可以在终端输入他的名字直接打开它,毕竟你已经把这个目录加到你的环境变量了。
在这里插入图片描述

点开文件里面有个装入。然后装入你拿到的堆内存快照。
在这里插入图片描述

可以看到下面的基本信息
在这里插入图片描述

查看异常的堆栈
在这里插入图片描述

看着这个异常的堆栈像是xxl-job的问题,不过不要慌,继续向下走。
查看最大的对象
在这里插入图片描述

在这里插入图片描述

排名第一的很明显是我们自己的类,感觉就要破案了。

点进去发现有个很大的oomMap,
在这里插入图片描述

找到自己的代码

在这里插入图片描述

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值