JVM OOM问题排查与解决思路
今天咱们来聊聊让无数 Java 开发者头疼的 JVM OOM(Out Of Memory,内存溢出)问题。
目录
一、JVM OOM 到底是什么?
二、OOM 为啥会发生?
三、OOM 都有哪些类型呢?
1.堆内存溢出
2.永久代/原空间溢出
3.栈内存溢出
4.直接内存溢出
四、排查 OOM 的“杀手锏”
通过MAT作为示例定位问题
0.获取dump文件
1.打开Heap Dump文件:启动MAT并打开Heap Dump文件(.hprof)
2.运行Leak Suspects Report :MAT可自动生成一个内存泄漏(Leak Suspects Report),该报告指出可能内存泄漏路径
3.分析Dominators Tree:该视图显示了占用最多的内存对象及其引用。通过1它,可找到最大的内存消耗者。
4.查看Histogram:列出所有对象的实例数和总大小,帮助识别那种类型的对象占用最多的内存
5.检查GC Roots:确定对象为什么没有被垃圾回收,可查看对象到GC Roots的引用链
6.分析引用链:通过分析对象的引用链,可确定是什么持有了这些对象的引用,导致它们无法被回收
通过jvisualvm定位OOM问题
通过Arthas工具定位问题
五、总结
一、JVM OOM 到底是什么?
简单来说JVM OOM就是Java虚拟机的内存用完了,而且垃圾回收器(GC)也无能为力,没办法再为新对象分配内存,于是抛出了错误。这就好比你开着一辆车,邮箱里的油已经耗尽,但你还想继续加速,结果只能熄火。
二、OOM 为啥会发生?
OOM的原因多种多样,但归根结底就两个字————“不够用”。具体来说,有这么几种常见情况:
1.内存分配不足:JVM初始化时。堆内存、永久代(或元空间)等区域分配得太小,根本不够业务跑。 比如,你的应用要处理海量数据,但堆内存只给了128MB,这不就是“杯水车薪”嘛。这种我们称不上bug,只是亦或是称为 “巧妇难为无米之炊”吧
2.大对象申请:一次性申请的内存太大,超出了JVM的承受范围。 比如,试图一次性加载一个几GB的文件到内存中,JVM根本就装不下。
3.内存泄漏:程序中某些地方申请了内存,但是因为代码逻辑错误,这些已经无用的内存却一直被引用,得不到释放,就像一个无底洞,不断吞噬JVM的内存。
4.代码问题:程序里某些对象被频繁创建,用完却没有被及时释放,导致内存被一点点占用,比如,一个定时任务不断往缓存中赛数据,但从来没清理过,时间一长,内存就被塞满了。
三、OOM 都有哪些类型呢?
1.堆内存溢出
这是OOM最常见的形式
错误信息是:
java.lang.OutOfMemoryError: Java heap spac
堆内存是JVM里存放对象实例的地方,如果堆内存满了,垃圾回收器又没办法清理足够的空间,就会触发这个错误。
2.永久代/原空间溢出
报错信息:
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Metaspace
在JDK7及以下版本中,永久代(PermGen)用于存放类的元数据、常量池等信息。如果应用加载了大量类,比如使用了动态代理、字节码操作等技术,永久代很容易被撑爆,抛出错误。从JDK 8 开始,永久代被原空间(Metaspace)取代,但原理类似。
核心原因:生成大量动态类
1.使用大量动态生成类的框架,如ORM框架,动态代理技术、热部署工具等
2.程序代码中大量使用,反射在大量使用时,由于使用缓存的原因,会导致ClassLoader和他引用的Class等对象不能被回收。
3.栈内存溢出
报错信息:
java.lang.OutOfMemoryError : unable to create new native Thread
栈内存是线程私有的,用于存放方法调用的局部变量、操作数栈等信息。如果一个方法调用链太深,比如递归调用过深,或者方法里的局部变量过多,栈内存就会溢出,虽然名字里面又“Overflow”,但本质也是OOM的一种。
4.直接内存溢出
报错信息:
java.lang.OutOfMemoryError: Direct buffer memory
Java堆外内存OOM值的是Java直接使用非堆内存耗尽导致的,这部分内存主要用于Java NIO库,允许Java程序以更接近操作系统的方式管理内存,常用于高性能缓存、大型数据处理等场景。
四、排查 OOM 的“杀手锏”
1.启动JVM诊断选项
在启动应用时,加上这些参数
-XX:+HeapDumpOnOutOfMemoryError // 指示JVM在遇到OOM错误时生成堆转储文件
-XX:HeapDumpPath=/path/to.dump //指定堆转储文件的存储路径,可以自定义路径和文件名
-Xlog:gc*(JVM 9及以上)
-XX:PrintGCDeails -Xloggc:/path/to/gc.log(JVM8及以下)
这些参数可以让JVM在OOM的时生成内存堆转储文件和GC日志,帮助我们分析问题。
2. 分析错误日志
仔细查看应用日志和OOM错误堆栈信息,看看哪个内存区域出了问题。
3.分析堆转储文件
用JVisualIVM、MAT这些工具打开堆转储文件,找出内存占用大户,看看是不是有点内存泄漏。
当应用抛出OOM并且根据上述设置生成了堆转储文件后,使用Heap Dump分析工具来分析文件,常用的工具有:
Eclipse Memory Analyzer(MAT): 一个强大的]ava堆分析工具,可以帮助识别内存泄漏和査看内存消耗等情况。
VisuaIVM: 除了监控功能外,也支持加载和分析Head Dump文件。
4.检查GC日志
分析GC日志,看看垃圾回收的概率、暂时时间和各内存区的使用情况,判断是不是垃圾回收出了问题。
5.代码审查和优化
从代码层找原因,看看是不是有缓存没清理、静态集合不断增长等内存泄漏问题。发现问题后,优化代码,减少对象创建,及时释放内存。
通过MAT作为示例定位问题
0.获取dump文件
注意导出文件占用内存很大的时候,可能会导不出来
方式一:提前配置JVM参数,在系统挂了后会在指定目录下生成dump文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=./ (导出路径)
方式二:使用命令行导出
jmap -dump:format=b,file=demo.hprof <pid>
1.打开Heap Dump文件:启动MAT并打开Heap Dump文件(.hprof)
2.运行Leak Suspects Report :MAT可自动生成一个内存泄漏(Leak Suspects Report),该报告指出可能内存泄漏路径
3.分析Dominators Tree:该视图显示了占用最多的内存对象及其引用。通过1它,可找到最大的内存消耗者。
4.查看Histogram:列出所有对象的实例数和总大小,帮助识别那种类型的对象占用最多的内存
5.检查GC Roots:确定对象为什么没有被垃圾回收,可查看对象到GC Roots的引用链
6.分析引用链:通过分析对象的引用链,可确定是什么持有了这些对象的引用,导致它们无法被回收
ps:授人以鱼不如授人以渔,en!有那味啦
MAT下载教程:
mac pro m1:安装dump文件内存分析工具——MAT_dump文件分析工具-CSDN博客
MAT使用教程~:线上又 OOM 了 ,教你快速定位问题~-腾讯云开发者社区-腾讯云
通过jvisualvm定位OOM问题
通过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 的关键在于启用诊断选项(如堆转储和 GC 日志)、分析错误日志和堆转储文件、检查垃圾回收日志。解决 OOM 的方法包括增加内存、优化代码、调优垃圾回收器参数和管理外部资源。持续监控和预警机制可以有效预防 OOM 问题的发生。