生产事故-那些年遇到过的OOM

📅 2026/7/1 2:49:08
生产事故-那些年遇到过的OOM
故障类型java.lang.OutOfMemoryError: Java heap space事故经过某考务管理系统前期收集考生报名信息时允许上传ZIP附件提交相关材料后台服务会解析压缩包并从中获取相关文件。系统运行后不久考务群就陆续有人反馈报名网站打不开无法访问等等。让运维重启系统后又恢复正常跑了一段时间以后又有人说无法访问。仔细检查故障时的日志发现故障时间点都是发生在有人上传ZIP文件的时候。从服务器上提取了一部分样本发现压缩文件里面包含若干个TXT文件TXT文件中是重复的字符类似AAA...该TXT文件原始数据巨大且单调重复导致压缩后的ZIP却非常小真是个天才直觉告诉我们这是被恶意攻击了遂暂时关闭了文件上传接口改为通过表单录入信息报名。事后复盘当时的代码发现处理ZIP文件时没有释放到磁盘临时文件都是在内存中直接解压并读取解压后的文本数据这就给了攻击者可乘之机。但是后来专门去研究了下这方面的安全漏洞发现这是一种ZIP炸弹ZIP of Death or ZIP Bomb即使是释放到磁盘也有可能造成磁盘资源耗尽。除了构造简单重复内容还能通过递归嵌套目录穿越等构造恶意的ZIP并释放巨量数据有兴趣的朋友可以去自行查阅。解决方案禁止上传嵌套压缩包只允许上传单级压缩文件检查文件大小检查文件路径。0x02 案例二事故时间2021年6月30日故障类型java.lang.OutOfMemoryError: Metaspace事故经过某报文处理服务需要同时处理多种渠道的XML报文使用了 JAXB (Java Architecture for XML Binding) 和 XSD (XML Schema Definition) 进行报文编/解组和格式检查。随着业务越来越繁重某次上线后生产服务频繁出现java.lang.OutOfMemoryError: Metaspace内存异常。最后经查是因为应用启动时一次性加载了全量的XSD和Document对象大量的加载类填满了Metaspace。应用JVM参数-XX:MaxMetaspaceSize、-XX:MetaspaceSize均设置为256MB当时的加载代码如下SchemaFactory schemaFactory SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return schemaFactory.newSchema(schemaSources);一次性初始化了所有的XSD源。老规矩先救命再治病临时解决方案评估最大Metaspace并扩容最终解决方案服务拆分分块加载0x03 案例三事故时间2022年3月17日故障类型java.lang.StackOverflowError事故经过某营销管理系统对接第三方接口上送的数据并进行解析处理触发对应的业务流程。其中一个业务处理是给编号为0-N的直连机构推送通知按照接口约定其中N由第三方接口指定且最大值不会超过255。管理后台采用了类似这样的代码进行处理public static void process(int corpNum) { try { System.out.println(发送通知给企业当前编号: corpNum); sendSms(corpNum); } catch (RuntimeException e) { System.err.println(发送通知给企业失败当前编号: corpNum); } if (corpNum ! 0) { process(corpNum - 1); } }上线之后系统一直运行良好直到有一天第三方接口上送数据时传了个-1嚯系统直接崩了打电话过去对方说是配置有误导致参数填写错误。这边喜提java.lang.StackOverflowError。其实测试之初应该可以避免的但是负责该业务的开发过于信任第三方上送的数据没有考虑到意外的参数范围狠狠的交了一笔学费。解决方案增加严格的参数校验同时修改尾递归写法为循环发送。0x04 案例四事故时间2022年4月15日故障类型java.lang.OutOfMemoryError: unable to create new native thread事故经过某接口服务配置了无界线程池作为业务线程池。该接口业务非常简单收集各个上游服务的度量指标 (Metrics) 简单记录日志并写入数据库轻量、高频、无长时间阻塞一切都那么完美。然而某天突然运维报告服务不可用查询日志发现服务已经凉了有段时间死因是java.lang.OutOfMemoryError: unable to create new native thread。还好留下了堆栈一通分析发现是有段时间应用日志所在磁盘空间写满导致线程得不到释放高频调用之下最终无法创建新线程导致服务被压垮。那么为什么写日志会阻塞线程呢当时应用使用的是logback日志实现查看其配置使用的是AsyncAppender异步记录器appender namefile.async classch.qos.logback.classic.AsyncAppender !-- 不丢失日志 -- discardingThreshold0/discardingThreshold appender-ref reffile.log/ /appender这里的配置正是压死骆驼的最后一根稻草。日志首先被写入BlockingQueue内存队列再由工作线程异步写入磁盘。如果磁盘写满导致下游FileAppender无法正常工作而AsyncAppender的队列又被填满就会导致对Logger的调用发生阻塞。官方文档里对于discardingThreshold是这样描述的In light of the discussion above and in order to reduce blocking, by default, when less than 20% of the queue capacity remains, AsyncAppender will drop events of level TRACE, DEBUG and INFO keeping only events of level WARN and ERROR. This strategy ensures non-blocking handling of logging events (hence excellent performance) at the cost loosing events of level TRACE, DEBUG and INFO when the queue has less than 20% capacity. Event loss can be prevented by setting the discardingThreshold property to 0 (zero).设置为0虽然可以防丢但也让logback没有退路可言。解决方案为接口配置有界线程池并调整discardingThreshold为合理数值。0x05 案例五事故时间2022年5月25日故障类型java.lang.OutOfMemoryError: Java heap space事故经过某后台管理系统由于存在敏感数据需要在本地安装安全控件来辅助访问该系统在首页上提供了多个版本的控件安装包下载。上线之初系统运行都挺正常但是某天突然有用户反馈系统无法访问浏览器提示502网关错误。查阅发现服务已挂应用日志提示java.lang.OutOfMemoryError: Java heap space使用MAT(Memory Analyzer Tool)工具分析dump文件发现存在大量的byte[]内存占用。结合应用日志发现服务异常之时正在调用某个文件下载方法该方法使用FileInputStream读取文件到内存中并使用byte[]数组存储文件内容 subsequent to将该byte[]数组写入到Response的输出流完成下载关键代码如下public static byte[] readFileContent(File file) { long fileLength file.length(); byte[] fileContent new byte[(int) fileLength]; try (FileInputStream in new FileInputStream(file)) { in.read(fileContent); return fileContent; } catch (Exception e) { logger.error(e.getMessage(), e); return null; } }短短几行代码却让人虎躯一震没有判断文件的大小就直接完整读取危险而且没有使用缓冲流的方式进行读写。事实证明问题恰恰就是出在这里某个版本的控件由于打包时体积偏大约200多MB导致多个用户同时下载时堆区内存一下子就被控件文件数据填满进而发生OOM异常。解决方案将控件安装包文件挂载到FTP上并提供外链不经过应用服务器下载。0x06 案例六事故时间2023年3月10日故障类型java.lang.OutOfMemoryError: Java heap space事故经过A公司开发人员在开发某开放接口时需要调用C公司的一个基础数据接口服务。然而从14时许开始A公司的接口调用就开始出现异常返回错误码500错误信息为java.lang.OutOfMemoryError: Java heap space。C公司开发人员向A公司开发人员反映某开放接口从14时许开始无法访问和使用。该系统为某基础数据接口服务基于HTTP协议进行通信。