一次线上cup高过导致OutOfMemoryError排查

本文讲述了如何处理生产环境中频繁出现的CPU过高和内存溢出问题,通过逐步排查,发现是操作Excel不当导致大量POI类占用内存。通过调整JVM内存、使用JavaVisualVM分析dump文件,最终定位并修复了内存泄漏问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

OutOfMemoryError问题排查
  • 近期生产环境每隔一段时间就会出现cup过高,导致服务不能访问
  • 再cup报警的时候通过top命令查看占用cup较高的应用
  • 找到对应应用的进程编号,dump对应内存文件jmap -dump:live,format=b,file=/mnt/app/heapdump.phorf pid
  • 通过jmap命令dump内存文件的失败了
  • 查看对应应用的异常信息在这里插入图片描述
  • 然后看一下系统对应fc的情况jstat -gcutil pid 1s
    在这里插入图片描述
  • 可以看到是一直在fgc,切并未清理出对应的空间
  • 为了不影响生产环境系统的使用,就加大JVM内存,对服务的重启
  • 把JVM内存调到了4G,并加上了内存溢出自动dump内存文件
export CATALINA_OPTS="$CATALINA_OPTS -server -Xms4g -Xmx4g -Xss256k -XX:+UseParallelOldGC -XX:+UseCMSCompactAtFullCollection  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/../logs/oom.hprof"
  • 重启服务之后看下gc的情况,gc正常
  • 在服务运行几天之后cup又报警了,自动dump了内存文件,看了下自动dump文件成功,手动改了下dump文件的名称(多节点配置,为了区分哪台服务器上的文件)
  • 看了下dump文件大小9.1G,把JVM内存调整到10G (dump文件是实际存活对象或未及时回收的对象所占的空间),重启服务之后对dump文件进行分析
    在这里插入图片描述
  • 把dump文件压缩之后,下载到本地
    使用jdk自带java VisualVM对dump文件进行分析
  • 加载dump文件之前,先设置下java VisualVM内存,在jdk下的lib目录下
    在这里插入图片描述
  • visualvm\etc下的visualvm.conf文件的visualvm default options配置
    在这里插入图片描述
  • 设置完内存时候打开java VisualVM,WIN+R 输入jvisualvm命令即可
    在这里插入图片描述
  • 点击文件,装入,选择下载到本地的dump文件
    在这里插入图片描述
  • 选择dump文件,点击打开即可
    在这里插入图片描述
  • 由于文件比较大,加载需要一段时间
  • 加载完之后之后可以看到异常信息
    在这里插入图片描述
  • 点击异常信息,可以直接跳转到异常信息详情
    在这里插入图片描述
  • 这里可以看到,我们自己的类,找到对应的代码定位问题处理即可
  • 这里我们看下内存对象的情况,点击【类】选项卡
    在这里插入图片描述
  • 点击对象,右键在【实例实图中显示】
    在这里插入图片描述
    在这里插入图片描述
  • 其实在【类】的页面可以看到org.apache.xmlbeans包下的类占比很大,全部加起来80%左右了,这个是poi相关的类,是操作excel不当导致的
    在这里插入图片描述
  • 然后排查代码,发现操作excel存在流关闭不当的情况
  • 调整代码之后,重新发布应用,查询cup情况
    在这里插入图片描述
  • 然后dump下内存文件,分析新的内存文件,并未发现过多poi对象
    在这里插入图片描述
  • 完善文件流关闭
  • 控制导出数据量,做分批次导出
  • JVM内存大小调整到2G,然后持续观察服务情况
  • 查看gc情况
    在这里插入图片描述
  • 查看内存使用情况jmap -heap pid
    在这里插入图片描述
  • 暂时未发现内存溢出的情况,后续在持续观察服务情况
<think>嗯,用户问的是线上服务出现OutOfMemoryError: Metaspace的可能原因。首先,我需要回忆一下Metaspace的作用。Metaspace在Java 8之后取代了永久代(PermGen),主要存储类的元数据,比如类的结构信息、方法信息、常量池等。所以Metaspace的内存不足通常和类加载有关。 那导致Metaspace溢出的常见原因有哪些呢?首先想到可能是加载了太多的类。比如,应用本身如果特别大,依赖很多库,尤其是动态生成类的情况,像使用反射、CGLIB、ASM等框架,可能会生成大量代理类。这些类一旦没有被及时卸载,就会占用Metaspace的空间。 另外,动态生成类的框架,比如Spring AOP,它在使用CGLIB进行代理时,会生成很多代理类。如果应用中有大量的AOP切面,或者频繁创建代理对象,可能会导致Metaspace被占满。这时候需要检查是否有不必要的代理生成,或者调整相关配置。 还有,Metaspace的大小配置问题。默认情况下,Metaspace的大小可能不够,特别是对于大型应用。如果用户没有设置-XX:MaxMetaspaceSize,Metaspace可能会动态增长,但受限于物理内存。如果物理内存不足,或者配置的最大值太小,就容易出现OOM。这时候需要检查JVM参数,看看是否有适当调整过MaxMetaspaceSize。 类加载器泄漏也是一个可能性。如果应用服务器或者框架频繁创建和销毁类加载器,而某些类加载器没有被正确回收,那么它们加载的类就无法被卸载,导致元数据堆积。例如,OSGi环境或者某些热部署机制下,如果没有处理好类加载器的生命周期,就可能出现这种情况。 还有第三方库或框架的问题。某些库可能在内部动态生成类,比如Hibernate、MyBatis等ORM框架,或者Groovy这样的动态语言,它们可能在运行时生成字节码,增加Metaspace的使用。如果这些库存在内存泄漏,或者使用不当,也会导致问题。 此外,JVM的类元数据本身占用内存,如果元数据本身有内存泄漏,比如JVM的bug,虽然比较少见,但也不能完全排除。这时候可能需要升级JVM版本,看看是否有相关的修复。 接下来,用户可能需要排查的方法。首先检查JVM参数,确认MaxMetaspaceSize是否足够。然后,通过内存分析工具,比如JDK自带的jstat,监控Metaspace的使用情况,看是否有持续增长的趋势。或者使用堆转储工具分析加载的类,看看是否有大量重复或意外的类存在。 还有,检查应用是否有使用动态代理、反射生成类的情况,特别是循环或频繁调用的代码路径中。此外,检查类加载器的使用情况,是否存在泄漏,比如未关闭的线程类加载器,或者未清理的缓存等。 可能用户的应用在最近有新增功能,引入了新的依赖库,或者增加了动态生成类的代码,导致Metaspace的需求突然增加。这时候需要回顾最近的变更,看看是否有相关的修改。 总结下来,可能的原因包括:Metaspace配置不足、动态生成大量类、类加载器泄漏、第三方库问题、JVM bug等。排查时需要结合监控数据、JVM参数、代码审查等多方面入手。</think>线上服务出现 `OutOfMemoryError: Metaspace` 通常与类的元数据(Metadata)占用过多内存有关。以下是可能导致该问题的常见操作和场景,以及排查思路: --- ### **1. 根本原因** Metaspace 存储类的元数据(如类名、方法信息、字节码、常量池等),当以下情况发生时可能触发 OOM: - **加载过多类**:JVM 无法卸载类,导致元数据堆积。 - **元数据内存泄漏**:类加载器未释放,导致其加载的类无法被回收。 --- ### **2. 常见操作与场景** #### **(1) 动态生成大量类** - **动态代理框架**:如 CGLIB、JDK Proxy(尤其 Spring AOP 默认使用 CGLIB 时)会生成代理类。 - **脚本引擎**:Groovy、JSP 等动态编译代码的场景。 - **反射生成类**:通过 `GeneratedMethodAccessor` 或 Unsafe API 生成类。 #### **(2) 类加载器泄漏** - **未关闭的类加载器**:如 Tomcat 热部署时旧的 `WebAppClassLoader` 未被回收。 - **自定义类加载器**:未正确管理生命周期,导致加载的类无法卸载。 #### **(3) 依赖库或框架问题** - **第三方库缺陷**:如 Hibernate、MyBatis 某些版本存在类加载问题。 - **重复加载类**:框架或代码逻辑错误导致同一类被多次加载。 #### **(4) JVM 配置不当** - **`-XX:MaxMetaspaceSize` 设置过小**:默认不限制(依赖物理内存),但显式设置过小会直接触发 OOM。 - **未监控 Metaspace**:未通过监控工具(如 Prometheus + JMX)观察 Metaspace 增长趋势。 --- ### **3. 排查步骤** #### **(1) 确认 JVM 参数** 检查是否显式设置 `-XX:MaxMetaspaceSize`,例如: ```bash -XX:MaxMetaspaceSize=256m # 默认无上限,建议根据实际情况设置 ``` #### **(2) 分析类加载情况** - **使用 `jcmd` 查看加载的类数量**: ```bash jcmd <pid> GC.class_stats | grep "Total" ``` - **通过 `jstat` 监控 Metaspace 使用率**: ```bash jstat -gcmetacapacity <pid> # 输出 Metaspace 容量统计 ``` #### **(3) 检查内存泄漏** - **生成堆转储文件(Heap Dump)**: ```bash jmap -dump:live,format=b,file=heapdump.hprof <pid> ``` - **分析工具**:使用 Eclipse MAT 或 JProfiler,过滤 `ClassLoader` 对象,检查是否有冗余或未释放的类加载器。 #### **(4) 代码与框架检查** - **动态代理**:检查 CGLIB 代理是否在循环中频繁生成。 - **热部署工具**:如 JRebel、Spring Boot DevTools,确认是否导致类重复加载。 - **类加载器使用**:确保自定义类加载器在完成任务后能及时被 GC 回收。 --- ### **4. 解决方案** - **调整 Metaspace 大小**:适当增加 `-XX:MaxMetaspaceSize`。 - **优化类生成逻辑**:减少动态代理类的生成(如改用 JDK Proxy)。 - **修复类加载器泄漏**:确保自定义类加载器的生命周期可控。 - **升级依赖库**:修复已知的类加载问题(如 Spring/Hibernate 版本升级)。 --- ### **5. 预防措施** - **监控报警**:对 Metaspace 使用率设置阈值报警。 - **定期分析**:在预发环境压测,观察类加载数量和 Metaspace 增长趋势。 - **代码规范**:避免在循环或频调用中动态生成类。 通过以上步骤,可逐步定位并解决 Metaspace OOM 问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值