参数设置错误导致的 OOM

参数设置错误导致的 OOM

  • 前言
  • 事故分析
  • 事故原因
  • 事故复盘


前言

2024 年 5 月 10 日 14 时 19 分,C 公司开发人员向 A 公司开发人员反映某开放接口从 2024 年 5 月 10 日 14 时许开始无法访问和使用。该系统为某基础数据接口服务,基于 HTTP 协议进行通信。按照惯例,首先排查网络是否异常,经运维人员检查,证明网络连通性没有问题。A 公司开发组于 2024 年 5 月 10 日 14 时 30 分通知运维人员重启应用服务,期间短暂恢复正常。但是,很快,十分钟后,电话再次响起,告知服务又出现异常,无法访问。为了避免影响进一步扩大,A公司决定将程序紧急回滚至上一稳定版本。回滚后,系统业务功能恢复正常。短暂松一口气后,开始排查问题。


事故分析

让运维拷贝和固定了更新前后的系统日志和应用包。根据前面的故障现象,初步猜测是内存问题,好在应用启停脚本中增加了参数-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/app/logs/app.dump(对于无法在生产环境上使用jstack、jmap等命令直接查错的——事实上大多数时候都不能,dump文件显得尤为重要),果不其然,日志目录下出现了app.dump文件,在日志中搜索,找到了若干处内存溢出错误java.lang.OutOfMemoryError: Java heap space,但是令人费解的是每次出现OOM错误的位置居然都不一样,事情逐渐变得复杂起来。

用 MAT(Memory Analyzer Tool) 工具打开转储文件,原以为会发现某个类型对象占用大量的内存,结果出乎意料,Histogram(直方图)中显示活跃对象居然只有100多M!尝试 Calculate Precise Retained Size(计算精确大小),计算结果与前面相差不大。检查 Outgoing References (追踪引用对象)和 Incoming References(追踪被引用对象)也未见明显异常,令人头大。

擦擦汗,日志已经明确提示我们java.lang.OutOfMemoryError: Java heap space,首先肯定这是一个堆内存空间引起的问题,可能的原因有:

内存加载数据量过大例如不受行数限制的数据库查询语句,或者不限制字节数的文件读取等,事故系统显然没有这些情况;内存泄漏(资源未关闭/无法回收)当系统存在大量未关闭的 IO 资源,或者错误使用ThreadLocal等场景时也会发生OOM,经排查,也不存在这种情况;系统内存不足系统内存不足以支撑当前业务场景所需要的内存,过小的机器内存或者不合理的JVM内存参数。

如果排除所有合理选项,最不合理那个会不会就是答案呢?遂开始检查机器的内存,根据运维的说法,机器内存为16GB,top命令查看java进程占用内存约为7.8GB,看起来似乎没毛病。

但是随后另一个同事注意到了一个事情,最后一次系统升级的时候,改动过应用启停脚本,对比旧版本的脚本,发现差异部分就是内存参数:

旧版本原为:

-Xms8g -Xmx8g -Xmn3g

-Xmx:设置堆的最大内存

-Xms:设置堆的初始内存大小

-Xmn:设置年轻代大小

新版本改为:

-Xms8g -Xmx8g -Xmn8g

看到这里,屏幕前的一众同事都无语啊……

事故原因

为什么-Xmn参数设置成与-Xmx参数一样的大小会导致 OOM 呢?该项目使用的 JDK 版本为1.8,看看 JDK 8 的内存模型:
在这里插入图片描述
不难发现,Heap Space Size = Young Space Size + Old Space Size,而-Xmn参数控制的正是 Young 区的大小,当堆区被 Young Gen 完全挤占,又有对象想要升代到 Old Gen 时,发现 Old 区空间不足,于是触发 Full GC,触发 Full GC 以后呢,通常又会面临两种情况:

Young 区又刚好腾出来一点空间,对象又不用放到 Old 区里面了,皆大欢喜
Young 区空间还是不够,对象还是得放到 Old 区,Old 区空间不够,卒,喜提OOM
诶,就是奔着 Old 区去的,管你 YoungYoungOld 区空间不够,卒,喜提OOM

这个就解释了为什么系统刚刚启动时,会有一个短时间正常工作的现象,随后,当某段程序触发 Old Gen 升代时,就会发生随机的OOM错误。那么什么时候对象会进入老年代呢?这里也很有意思,不妨结合日志里面出现OOM的地方,对号入座:

经历足够多次数 GC 依然存活的对象
申请一个大对象(比如超过 Eden 区一半大小)
GCEden 区对象大小超过 S 区之和
Eden+ S0GC 后,S1 区放不下

换言之,正常情况下,-Xmn参数总是应当小于-Xmx参数,否则就会触发OOM错误。我们可以构造一个简单的例子来验证这个场景。首先是一个简单的SpringBoot程序:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;@SpringBootApplication
public class OomApplication {static final byte[] ARRAY = new byte[128 * 1024 * 1024];public static void main(String[] args) {SpringApplication.run(OomApplication.class, args);}@RestControllerpublic static class OomExampleController {@GetMapping("/oom")public int oom() {byte[] temp = new byte[128 * 1024 * 1024];temp[0] = (byte) 0xff;temp[temp.length - 1] = (byte) 0xef;int noise = new Random().nextInt();ARRAY[0] = (byte) (temp[0] + temp[temp.length - 1] + noise);return ARRAY[0];}}
}

使用mvn clean package命令打包后,我们用下面的命令启动它:

java -Xms512m -Xmx512m -Xmn512m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar oom-1.0.0-RELEASE.jar

然后借助Apache的ab.exe,完成我们的验证测试。先是以1个并发访问100次上面的SpringBoot接口:

ab -c 1 -n 100 http://localhost:8080/oom

你会发现,它居然是可以正常运行的,然后我们模拟用户负载上来之后的情况,使用2个并发访问100次:

ab -c 2 -n 100 http://localhost:8080/oom

如果前面的步骤都没错,此时应该在 SpringBoot 应用控制台看到大量的 OOM 错误,如下图所示:
在这里插入图片描述
然后在 GC 日志里面会看到,触发 GC 的前后,Old 区几乎都没有空间,仅有的一点点还是 JDK 强行分配的(在启动 JVM 时强制覆写了我们的-Xmn参数):
在这里插入图片描述
接着无需改动任何代码,我们调整下启动参数,像这样:

java -Xms512m -Xmx512m -Xmn64m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:gc.log -jar oom-1.0.0-RELEASE.jar

你会发现它又可以了。这是一个为了验证而打造的极端例子,实际上生产的应用情况会比这个复杂得多,但这并不妨碍我们理解它的意图。

事故复盘

这是一场典型的”人祸“,来源于某个同事的”调优“,比起追究责任,更重要的是带给我们的启发:

即使是应用启停脚本,也应该作为程序的一部分,纳入测试验证流程和上线检查清单,禁止随意变更;
很多时候,默认的就是最好的,矫枉则常常过正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/846057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux安装MYSQL后,利用grep查看MYSQL初始密码

问题描述 linux安装mysql获取初始密码 解决方案: 通过查看日志获取初始密码 grep "password" /var/log/mysqld.loggrep 是一个用于在文本中查找特定字符串的工具。 /var/log/mysqld.log 是要搜索的文件路径,"password" 是要查找的…

CMake的作用域:public/private/interface

在 CMake 中,public、private和 interface是用来指定目标属性的作用域的关键字,这三个有什么区别呢?这些关键字用于控制属性的可见性和传递性,影响了目标之间的依赖关系和属性传递。 public 如果在一个目标上使用 public关键字时…

CTFHUB-信息泄露-目录遍历和PHPINFO

目录 目录遍历 PHPINFO 目录遍历 很简单,挨着把每个目录都点开看一下 发现2目录下有个 flag.txt 文件,点开发现了本关的flag PHPINFO 这关也很简单,进来之后是一个phpinfo页面,按 CTRL F键打开查询,输入flag&#…

window本地部署Dify

Dify与之前的MaxKB不同,MaxKB可以实现基础的问答以及知识库功能,但是如果要开发一个Agent,或者工作流就还是需要额外开发,而Dify 是一个开源 LLM 应用开发平台。其直观的界面结合了 AI 工作流、RAG 管道、代理功能、模型管理、可观…

一个不错的讲解做竞品分析的方法

比如选了竞品1,竞品2,然后每个功能项,选定1个做标准被比较的锚点,比如外观,用竞品2,设置为1分,然后看竞品1,在外观的评分上,相比竞品2,是分数低点还是高点&am…

缓冲区溢出攻击

缓冲区溢出攻击 缓冲区溢出概述基础概念缓冲区溢出根源缓冲区溢出危害性&普遍性 缓冲区溢出攻击原理内存分配模式缓冲区溢出攻击缓冲区溢出攻击原理缓冲区溢出攻击分类堆栈溢出堆栈相关知识攻击原理 堆溢出攻击堆简介堆溢出DWORD SHOOT BSS段溢出 缓冲区溢出攻击防御措施防…

Frida使用与解题

对于 Android 逆向,首先需要熟悉对于 adb 基本命令使用 1.C:\Users\sun>adb shell ASUS_I003DD:/ # getprop ro.product.cpu.abi x86_64 查看架构 exit 退出 2. adb push "E:\reverse\ida\IDA_Pro_7.7\IDA_Pro_7.7\IDA_Pro_7.7\dbgsrv\android_x86_ser…

LeetCode162寻找峰值元素

题目描述 峰值元素是指其值严格大于左右相邻值的元素。给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。你可以假设 nums[-1] nums[n] -∞ 。你必须实现时间复杂度为…

09Linux GDB学习笔记

Linux GDB使用 目录 文章目录 Linux GDB使用先编译文件1.检查安装1.1 安装GDB 2.启动GDB3.退出GDB4.设置断点4.1 在指定行号处设置断点4.2 在指定函数名处设置断点4.3 在指定源文件和行号处设置断点 4.4查看断点信息4.5删除断点5.运行5.1 <font color#ff0000>逐过程&am…

认识JAVA中的异常

目录&#xff1a; 一. 异常概念与体系结构 二. 异常的处理 三. 自定义异常类 一. 异常概念与体系结构: 1 异常的概念:在 Java 中&#xff0c;将程序执行过程中发生的 不正常行为 称为异常&#xff0c; 如&#xff1a;算数异常&#xff1a; ArithmeticException System.out.pri…

大豆、棉花深度学习数据集大合集

最近收集了一大波关于大豆和棉花的深度学习数据集&#xff0c;主要有叶片的识别、分类、计数以及病害检测等。 数据集的价值 科研价值&#xff1a;这些数据集为植物学、农业信息技术、机器学习等领域的科研人员提供了宝贵的资源。它们可以用于训练和优化各种深度学习模型&…

CCF CAT- 全国算法精英大赛(2024第二场)往届真题练习 4 | 珂学家

前言 餐馆 思路&#xff1a;可撤销的0-1背包 考察了多个知识点&#xff0c;包括 差分技巧离线思路0-1背包 不过这题卡语言&#xff0c;尤其卡python import java.io.*; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream;public…

C#开发的应用升级更新服务器端工具 - 开源研究系列文章 - 个人小作品

笔者开发过一些小应用&#xff0c;然后这些应用就需要有升级更新的功能&#xff0c;但是如果每个都集成进去也行&#xff0c;但是就是得写死更新的代码了。于是就想写一个应用升级更新的管理器&#xff0c;以前看到过Github上有一个AutoUpdate.Net&#xff0c;不过它那个要集成…

2024最新VMware Workstation Pro下载教程

自从2024年5月份之后&#xff0c;VMware workstation player就不能直接在vm官网下载,需要到broadcom博通网站上下载 下面介绍最新下载步骤&#xff1a; 百度直接搜索vmware 进入官网点击Workstation Pro链接 博通注册对应的账号 现在下载都需到博通注册对应的账号 登录邮…

展台设计需要注意哪些问题

一、明确设计目标与主题 在设计之前需要明确展台设计目标和主题。设计目标是指展台设计所要达到的目的和效果&#xff0c;提高企业知名度、促进产品销售等&#xff1b;设计主题则是展台设计的核心和灵魂&#xff0c;贯穿整个展台设计的风格和形式。参展企业需要清晰地界定设计目…

Linux常用环境Docker安装

一、mysql安装 简单安装 docker run -d \--name mysql \-p 3306:3306 \-e TZAsia/Shanghai \-e MYSQL_ROOT_PASSWORD123 \mysql mysql容器本地挂载 cd /usr mkdir mysql cd mysql/ mkdir data mkdir conf mkdir init可以手动导入自己的数据库信息 docker run -d \--name mys…

@Transactional事务注解内含乾坤?

文章目录 前置知识实战1、无事务操作2、事务操作 总结 前置知识 MyBatis中的工作原理 读取配置文件&#xff1a;数据库连接信息、事务管理信息以及映射文件的位置加载映射文件&#xff1a;加载Mapper.xml这些映射文件&#xff0c;解析其中的 SQL 语句和结果映射信息创建 SqlS…

如何在Windows 10上的命令提示符下更改目录?这里提供方法

如果你刚开始在Windows 10上使用命令提示符,你需要知道如何更改目录。 如何在Windows上打开命令提示符 随着你对Windows 10上的命令提示符越来越熟悉,你需要学习的第一件事是如何更改操作系统文件系统中的目录。有几种方法可以做到这一点,所以我们将带你了解它们。 注意:…

JCR一区级 | Matlab实现TCN-LSTM-MATT时间卷积长短期记忆神经网络多特征分类预测

JCR一区级 | Matlab实现TCN-LSTM-MATT时间卷积长短期记忆神经网络多特征分类预测 目录 JCR一区级 | Matlab实现TCN-LSTM-MATT时间卷积长短期记忆神经网络多特征分类预测分类效果基本介绍程序设计参考资料 分类效果 基本介绍 1.JCR一区级 | Matlab实现TCN-LSTM-MATT时间卷积长短…

问界新M7 Ultra仅售28.98万元起,上市即交付

5月31日&#xff0c;问界新M7 Ultra正式上市。发布会上&#xff0c;鸿蒙智行旗下多款产品交出最新答卷——问界新M5上市1个月大定突破2万台&#xff1b;智界S7位列30万纯电轿车4月交付量NO.3&#xff1b;问界M9上市5个月大定突破9万台。其中&#xff0c;作为中国高端豪华SUV市场…