排查和解决JVM OOM实战
JVM OOM介绍
Java内存区域布局
下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存,也可能是因为元空间或直接内存泄漏导致OOM,此时在OOM的详细信息中会有不同体现。
Java OOM的类别
java.lang.OutOfMemoryError: GC overhead limit exceeded
JVM 98%的CPU时间都在执行GC,并且每次GC回收空间小于堆内存的2% 基本就是应用不可用的状态了!
可能原因:1.堆内存设置过小 2.内存泄漏
java.lang.OutOfMemoryError: Java heap space
JVM 堆内存无法再分配对象。
可能原因:1.堆内存设置过小 2.内存泄漏 3.对象实现finalize方法不当,回收速度跟不上finalization queue入队速度
java.lang.OutOfMemoryError: Metaspace
元空间是直接用本地内存,受限于MaxMetaSpaceSize参数。存放的类对象信息过多导致元空间溢出。
可能原因:1.元空间过小 2.动态代理造成类对象过多
java.lang.OutOfMemoryError: request size bytes for reason. Out of swap space?
很少出现。通常是JVM的本地堆内存无法再分配对象导致。
java.lang.OutOfMemoryError: Compressed class space
很少出现。
java.lang.OutOfMemoryError: reason stack_trace_with_native_method
很少出现。摘自Orcale JDK Document
If the detail part of the error message is "reason stack_trace_with_native_method" and a stack trace is printed in which the top frame is a native method, then this is an indication that a native method has encountered an allocation failure. The difference between this and the previous message is that the allocation failure was detected in a Java Native Interface (JNI) or native method rather than in the JVM code.
沃尔玛支付中台实际案例
我在沃尔玛中国的科技部工作的时候,负责过支付中台的研发,我接手的时候还是一个刚起步的系统。我负责的一年半时间中,接入门店从2家扩展到全国200家门店。日支付单量从7、8千到80~100万的规模。为全国的山姆会员店自助收银机、沃尔玛大卖场自助收银机、线上山姆会籍系统提供支付退款结算能力。中间的过程不是一帆风顺,在一个元旦假期还遇到OOM的线上事故(后面排查出是前同事的老代码导致),造成门店大面积停用自助收银机,顾客被迫转向人工收银和小程序线上缴费等其它支付通道。
问题表现
第一次发生OOM问题时候,排查日志发现OOM前十分钟,日志显示请求进来的线程都在运行到某个时刻全部被hoding。再也没有日志输出,原本业务代码中正常时刻会输出日志。直到OOM错误出现。整个服务表现为不可用状态。后来导出的jstack日志显示所有线程都是BLOCKED状态。观察日志发现线程号貌似增长到50+。Kevin判断是请求外部接口时遇到了很大的延迟导致线程没释放而外部请求堆积越来越多,最终消耗完了内存。
于是按照这个判断给HTTP接口加上限流和告警监控,毕竟没有办法干预外部第三方支付接口。以为事情就解决了,但是......
在元旦假期的时候下午5点支付中台又发生了OOM。6g的堆内存,4g的old gen全部拉满,观察听云old gen从昨天一直满的到现在。查看full gc频率,基本20分钟内发生200+次full gc,查看日志也是显示 GC overhead limit exceeded error 代表应用98%的时间都在执行GC,并且每次GC回收空间小于堆内存的2% 基本就是应用不可用的状态了!
门店也反馈收银支付十分慢,表现就是卡死!周一被军训和被吊打是免不了的了。最后排查发现是前同事擅自改了银联SDK,把CertUtil从静态类改成对象,每次银联请求时都创建对象,直接触发了底层的一个坑。
排查过程
基于基础知识我们知道,OOM的类别中GC overhead limit exceeded error,很大概率是老年代堆内存发生了泄漏,导致GC一直没办法有效释放对象,进而引发不断地Full GC,整个服务用户进程的CPU时间都用在GC线程上了,用户线程反而得不到CPU时间,服务就假死了。立即新启动几个服务节点顶上,新节点内存会缓慢的上升直至OOM,中间有一段可用时间,可以恢复服务能力。
排查内存泄漏,一般无法从代码看出来哪里泄漏了。所以我们得想办法拿到内存快照,用:jmap -dump:live,format=b,file=filename.bin pid
这个命令可以导出堆内存中存活的对象,因为我们排查内存泄漏所以只要求存活对象。使用前注意:jmap为了收集准确的堆内存使用情况会暂停jvm的用户线程和gc线程。所以造成STW。具体STW时长取决于导出的堆内存大小正相关。
关于jmap是否会造成STW可看stackoverflow一篇文章:
java - Is a JVM stopped while executing jmap? - Stack Overflow
jmap导致stw造成生产事故的例子:
memory - Java : Get heap dump without jmap or without hanging the application - Stack Overflow
其中文章内提了一些避免STW同时获取堆内存快照的做法,例如用gdb命令。fork进程,然后在子进程dump,fork可能会占用大量额外内存在主进程同时对同一个地址空间的内存大量写动作,就会有copy-on-wirte发生,会分配新内存页copy旧的内存页让主进程在新的内存页写入。
以上做法其实都不推荐,也没有实践过。正确且成熟的做法应该是在网关层或负载层把机器节点摘掉,验证没有流量后,再使用jmap导出堆内存快照,这时候你想怎么搞就怎么搞。
当晚等到新节点内存涨到一定规模后,即将发生FGC,摘流用jmap导出内存快照,下载到本地分析。每台服务器两个快照,一共4个。最大的快照8.4G。把快照下到本地,用MAT打开分析。提前要调整好MAT的maxHeapSize,不然MAT无法装载快照。
把快照导入后,8.4G大概加载了30分钟,然后就按照提示选择 Leak Suspects 分析,完毕后会给出内存对象占用空间的饼图,并且给出 problem a b 等推测泄漏对象/类。基本逐个查看泄漏对象的details,例如看这个对象的dominator tree等等
使用MAT分析泄漏点
本次事故中,在Problem Suspect 中很明显可以看到是一个 JceSecurity对象占了5.2G内存,这肯定是不正常的。果然就是内存泄漏了。details中在支配树中看到JceSecurity有一个IdentityHashMap内部用的Object[]数组保存Entry。这个数组中有6000+个BouncyCastleProvider对象,总共占了5.2G内存。为什么这个对象不会被回收?进一步看这个对象的 Path to GC roots,排除所有弱引用、虚引用。可以看到gc root就是JceSecurity对象,这个对象MAT提示是System class,是一个GC root不会被回收的。
结合源码看,JceSecurity里的map是一个static final修饰,map是常量的,也就是6000+多个provider对象被map持有强引用所以一直释放不了。
OK!这就很明显的泄漏就是这个provider对象了,但是这里只找到了泄漏对象。这个对象我也不认识,也不记得代码里哪里用到了它。
根据网上的教程,也没通过MAT查找到源码级别哪个地方泄漏了provider,幸运地在代码里全局搜索BouncyCastleProvider,很快在银联的SDK中找到这个对象的使用。并且是Security.add(new BouncyCastleProvider()) 这样明显的代码。再看这段代码是每次创建CertUtils对象会执行的代码。很自然怀疑是这个地方,于是在本地复现,简单的死循环调用 Security.add(new BouncyCastleProvider()),通过VMVisual监控JVM进程的堆内存变化,很正常的释放,provider对象数量涨到一定程度又降下去了。
难道不是这里?最后在QA服务器,压测银联的查询接口,jstat监控堆内存的使用量,几十分钟后发现old gen一直无法释放内存,并且开始频繁full gc。dump了内存快照到本地查看,果然和生产的快照显示一样,是JceSecurity占了大部分内存!
那么就可以确定是银联CertUtil的问题,查看官网的原对象使用,人家是静态类,会在static代码块调用一次 Security.add(new BouncyCastleProvider())。但是我们的代码是每次创建CertUtil造成每次调用Security.add(new BouncyCastleProvider())。
但问题是为什么死循环没复现泄漏问题呢?后来查资料可能是没有使用加解密代码,并且用VMVisual查看JceSecurity对象没有生成,虽然产生了很多provider但因为没有强引用,都被GC了。
结果
最后通过修正相关代码,避免每次都add Provider。再次发布QA,压测银联查询接口,dump快照,没有发现大量占内存的对象。jstat观察old gen也基本平缓,没有full gc产生。发布生产环境,跑一段时间再看看。到这里基本可以确定是修复了这个泄漏问题了。
总结排查手段和思路
如果一个服务发生了OOM,要判断是什么类型的OOM。然后对症排查分析。我们日常工作中最常出现的就是堆内存的OOM。先看是内存设置是否合理,其次看是否流量暴增导致,最后要怀疑内存泄漏。
如果是内存泄漏,可以通过jstat先简单查看GC情况,表现应为old区经历了FGC或old gc仍然无法释放内存,并且连续每次几乎都是这种情况,就可以高度怀疑是内存泄漏。通过摘流、jmap导快照、MAT分析、源码分析一套连招下来,大部分内存泄漏可以被定为到泄漏点然后修复。
找到泄漏点后更严谨的,需要在本地环境或QA环境进行复现。然后修复后再次进行复现,验证是否修复。
相关文章:

排查和解决JVM OOM实战
JVM OOM介绍 Java内存区域布局 下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存,也可能是因为元空间或直接内存泄漏导致OOM,此时在OOM的详细信息中会有不同体现。 Java OOM的类别 java.lang.OutOfMemory…...
【Swift官方文档】7.Swift集合类型
集合类型 使用数组、集合和字典来组织数据。Swift 提供了三种主要的集合类型:数组、集合和字典,用于存储值的集合。数组是有序的值集合。集合是无序的唯一值集合。字典是无序的键值对集合。 Swift 中的数组、集合和字典始终清晰地指明它们可以存储的值…...

QT调用最新的libusb库
一:下载libusb文件 下载最新的库的下载网站:https://libusb.info/ 下载: 解压后目录如下: 二:库文件添加QT中 根据自己的编译器选择库: ①将头文件中添加libusb.h ②源文件中添加libusb-1.0.lib ③添加…...

白嫖EarMaster Pro 7简体中文破解版下载永久激活
EarMaster Pro 7 简体中文破解版功能介绍 俗话说得好,想要成为音乐家,就必须先拥有音乐家的耳朵,相信很多小伙伴都已经具备了一定的音乐素养,或者是说想要进一步得到提升。那我们就必须练好听耳的能力,并且把这种能力…...

使用JavaScript写一个网页端的四则运算器
目录 style(内联样式表部分) body部分 html script 总的代码 网页演示 style(内联样式表部分) <style>body {font-family: Arial, sans-serif;display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #f0f0f0;}.calculator {…...
Linux find命令详解及实用示例
Linux 系统中的 find 命令是一个功能强大的工具,用于在文件系统中搜索文件并执行相应的操作。无论是系统管理员还是普通用户,掌握 find 命令都能极大地提高工作效率。本文将详细介绍 find 命令的用法,并通过多个示例展示其在实际中的应用。 …...

CSS基础-常见属性(二)
6、CSS三大特性 6.1 层叠性 如果样式发生冲突,则按照优先级进行覆盖。 6.2 继承性 元素自动继承其父元素、祖先元素所设置的某些元素,优先继承较近的元素。 6.3 优先级 6.3.1 简单分级 1、内联样式2、ID选择器3、类选择器/属性选择器4、标签名选择器/…...
Spring Boot 2.4.3 + Java 8 升级为 Java 21 + Spring Boot 3.2.0
简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 Spring Boot 2.4.3 + Java 8 升级为 Java 21 + Spring Boot 3.2.0前言更换 Java 21 SD…...

如何利用免费音频剪辑软件制作出精彩音频
现在有许多免费的音频剪辑软件可供选择,它们为广大用户提供了丰富的功能和便捷的操作体验,让音频编辑变得更加轻松和有趣。接下来,让我们一起走进这些免费音频剪辑软件的世界,探索它们的独特魅力和强大功能。 1.福昕音频剪辑 链…...

安宝特分享 | AR技术重塑工业:数字孪生与沉浸式培训的创新应用
在数字化转型的浪潮中,AR(增强现实)技术与工业的结合正在呈现新的趋势和应用延伸。特别是“数字孪生”概念的崛起,为AR技术在工业中提供了独特而创新的切入点。 本文将探索AR如何与数字孪生、沉浸式体验和实用案例相结合…...

专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
目录 搜索 vs 深度优先遍历 vs 深度优先搜索 vs 宽度优先遍历 vs 宽度优先搜索 vs 暴搜 1.深度优先遍历 vs 深度优先搜索(dfs) 2.宽度优先遍历 vs 宽度优先搜索(bfs) 2.关系图暴力枚举一遍所有的情况 3.拓展搜索问题全排列 决策树 1. 计算布尔⼆叉树的值(medi…...

基于springboot vue3 在线考试系统设计与实现 源码数据库 文档
博主介绍:专注于Java(springboot ssm springcloud等开发框架) vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…...

什么是 HTTP 请求中的 options 请求?
在 Chrome 开发者工具中的 Network 面板看到的 HTTP 方法 OPTIONS,其实是 HTTP 协议的一部分,用于客户端和服务器之间进行“预检”或“协商”。OPTIONS 请求的作用是让客户端能够获取关于服务器支持的 HTTP 方法和其他跨域资源共享 (CORS) 相关的信息&am…...

[图形学]smallpt代码详解(1)
一、简介 本文介绍了著名的99行代码实现全局光照的光线跟踪代码smallpt。 包括对smallpt的功能介绍、编译运行介绍,和对代码的详细解释。希望能够帮助读者更进一步的理解光线跟踪。 二、smallpt介绍 1.smallpt是什么 smallpt(small Path Tracing) 是一个全局光照…...

Vite多环境配置与打包:
环境变量必须以VITE开头 1.VITE_BASE_API: 在开发环境中设置为 /dev-api,这是一个本地 mock 地址,通常用于模拟后端接口。 2.VITE_ENABLE_ERUDA: 设置为 "true",表示启用调试工具,通常是为了…...

git维护【.gitignore文件】
在工程下添加 .gitignore 文件【git忽略文件】 *.class .idea *.iml *.jar /*/target/...

【Canvas与色彩】十六等分多彩隔断圆环
【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>隔断圆环Draft5十六等分多彩</title><style type"text…...

什么是pip? -- Python 包管理工具
前言 不同的编程语言通常都有自己的包管理工具,这些工具旨在简化项目的依赖管理、构建过程和开发效率,同时促进代码的复用和共享。每个包管理工具都有其独特的特点和优势,开发者可以根据自己的编程语言和项目需求选择合适的包管理工具。 pip是…...

FastAPI框架使用枚举来型来限定参数、FastApi框架隐藏没多大意义的Schemes模型部分内容以及常见的WSGI服务器Gunicorn、uWSGI了解
一、FastAPI框架使用枚举来型来限定参数 FastAPI框架验证时,有时需要通过枚举的方式来限定参数只能为某几个值中的一个,这时就可以使用FastAPI框架的枚举类型Enum了。publish:December 23, 2020 -Wednesday 代码如下: #引入Enum模块 from fa…...

OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,当初有的问题未解决,目前新版未尝试
OceanBase—02(入门篇——对于单副本单节点,由1个observer扩容为3个observer集群)——之前的记录,有的问题未解决,新版未尝试 1、前言—安装单副本单节点集群1.1 docker安装OB 2、查看现有集群情况2.1 进入容器&#x…...

网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
《C++ 模板》
目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板,就像一个模具,里面可以将不同类型的材料做成一个形状,其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式:templa…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...