当前位置: 首页 > news >正文

【SpringBoot项目OOM记录及处理】

SpringBoot项目OOM记录及处理

  • 一、背景
  • 二、临时处理措施
  • 三、准备分析dump文件
  • 四、堆外内存
  • 五、sun.reflect.GeneratedMethod
  • 六、处理方案

一、背景

  1. java 环境 java -version openjdk version “1.8.0_412”
  2. centos版本 cat /etc/centos-release CentOS Linux release 7.8.2003 (Core)
  3. 项目正常运行一段时间后,突然502无法访问,去后台观察服务,已经挂掉了,但是当时启动并没有添加JVM参数,所以SpringBoot的logback日志并没有打印为什么挂掉的日志,只能去/var/log/messages中去查看系统进程日志 grep -i "killed process" /var/log/messages ,发现服务已经OutOfMemoryError了。

二、临时处理措施

  • 添加JVM启动参数,并准备分析dump文件
# --server.port 指定启动端口
# -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/logs/oom.hprof 写入OOM日志
nohup java -jar -Xmx2048m -Xms1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/logs/oom.hprof -Dserver.port=66666 test.jar >/dev/null 2>&1 &

三、准备分析dump文件

  1. jmap -dump:live,format=b,file=heapdump3.hprof 1267 1267是服务PID,但是需要注意该命令会引起full GC,尽量避免生产环境少人的时候使用,主要分析hprof这个堆内存dump文件
  2. jstack 1267 > thread_dump.txt ,生成线程dump文件分析cpu居高不下的问题,但是本服务cpu正常,所以基本上不是线程问题
  3. 使用jprofile进行分析heapdump3.hprof文件
    在这里插入图片描述
  4. 但是堆内存很正常,如果不正常的话,可以通过选中占比最大的class,然后鼠标右键点击Use selected instance去分析引用该class的地方在这里插入图片描述
  5. 查出是否堆内存泄漏问题,但是发现不是。

四、堆外内存

  1. 如果堆内存没有泄漏,那么就要考虑是否是堆外内存引起的问题
  2. 由于我设置了Xmx和Xms,但是服务还是超过了Xmx内存,且堆内存并没有明显泄漏的地方,基本可以确定就是堆外内存,也就是metaspace泄漏,在java1.8中,XX:MaxMetaspaceSize是没有上限的,所以如果metaspace泄漏,很容易oom
  3. 分析堆外内存
  4. jmap -heap pid 用来分析jvm的实际配置
Attaching to process ID 1474, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.66-b17

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 4294967296 (4096.0MB)
   NewSize                  = 2147483648 (2048.0MB)
   MaxNewSize               = 2147483648 (2048.0MB)
   OldSize                  = 2147483648 (2048.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 1932787712 (1843.25MB)
   used     = 1698208480 (1619.5378112792969MB)
   free     = 234579232 (223.71218872070312MB)
   87.86316621615607% used
Eden Space:
   capacity = 1718091776 (1638.5MB)
   used     = 1690833680 (1612.504653930664MB)
   free     = 27258096 (25.995346069335938MB)
   98.41346682518548% used
From Space:
   capacity = 214695936 (204.75MB)
   used     = 7374800 (7.0331573486328125MB)
   free     = 207321136 (197.7168426513672MB)
   3.4349974840697497% used
To Space:
   capacity = 214695936 (204.75MB)
   used     = 0 (0.0MB)
   free     = 214695936 (204.75MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 2147483648 (2048.0MB)
   used     = 322602776 (307.6579818725586MB)
   free     = 1824880872 (1740.3420181274414MB)
   15.022362396121025% used

29425 interned Strings occupying 3202824 bytes
  1. 1.8后,Hotspot虚拟机已经移除了永久代,使用了元空间代替。」 由于我们线上使用的是JDK1.8,「所以我们对于元空间的最大容量根本就没有做限制」在这里插入图片描述
  2. 在本地开发环境中加入对应参数-XX:+TraceClassLoading -XX:+TraceClassUnloading -verbose:class -XX:MaxMetaspaceSize=100m 开始打印装载class的信息
  3. 然后就发现不停的打印[Loaded sun.reflect.GeneratedMethodAccessor191 from __JVM_DefineClass__]还有由于超过了MaxMetaspaceSize=100m导致的unloading信息
  4. 然后再去线上分析jmap -clstats 1267 发现也有大量的sun.reflect.GeneratedMethod相关class
  5. 通过java 自带的visualVM分析metaspace空间在这里插入图片描述
    10.发现随着 [Loaded sun.reflect.GeneratedMethodAccessor191 from __JVM_DefineClass__]不断增长,metaspace也越来越大

五、sun.reflect.GeneratedMethod

  1. Java反射调用在多次执行后,JVM会生成字节码来提高性能,即GeneratedMethodAccessor和GeneratedConstructorAccessor。这些类会被加载到Metaspace,导致内存问题。默认15次
  2. 当使用Java反射时,Java虚拟机有两种方法获取被反射的类的信息。它可以使用一个JNI存取器;如果使用Java字节码存取器,则需要拥有它自己的Java类和类加载器(sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader),这些类和类加载器使用本机内存。字节码存取器也可以被JIT编译,这样会增加本机内存的使用。如果Java反射被频繁使用,会显著地增加本机内存的使用。
  3. Java虚拟机会首先使用JNI存取器,然后在访问了同一个类若干次后,会改为使用Java字节码存取器。这种当Java虚拟机从JNI存取器改为字节码存取器的行为被称为膨胀(Inflation)。Inflation机制提高了反射的性能,但是对于重度使用反射的项目可能存在隐患,它带来了两个问题:(1)初次加载的性能损失;(2)动态加载的字节码导致PermGen持续增长。幸运的是,我们可以通过一个设置-Dsun.reflect.inflationThreshold=N控制这种行为,sun.reflect.inflationThreshold会告诉Java虚拟机使用JNI存取器多少次。

六、处理方案

  1. -Dsun.reflect.inflationThreshold=2147483647 设置启动参数使得只使用JNI存取器
  2. 升级到java17
  3. 完整启动参数nohup java -jar -Xmx2048m -Xms1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/logs/oom.hprof -Dsun.reflect.inflationThreshold=2147483647 -Dserver.port=66666 test.jar >/dev/null 2>&1 &
  4. 需要注意不能使用-Dsun.reflect.noInflation=true 会导致直接生成字节码文件,而不使用JNI存取器

相关文章:

  • 如何重装windows系统
  • AppInventor2生成3位数的水仙花数
  • Python期末复习题库
  • Android 13系统定制实战:基于系统属性的音量键动态屏蔽方案解析
  • mysql 主从配置
  • XSS通关技巧
  • 创建vue2项目
  • 【MYSQL】索引和事务
  • vue 新项目安装依赖插件
  • Vue3 实战:基于 mxGraph 与 WebSocket 的动态流程图构建
  • WebSocket 的错误处理与断线重连
  • 5.1 位运算专题:LeetCode 面试题 01.01. 判定字符是否唯一
  • QT编程之PCM音频播放与采集
  • vue3 项目的最新eslint9 + prettier 配置
  • Android获取U盘路径
  • Python+Requests+Pytest+YAML+Allure接口自动化框架
  • 从国家能源到浙江交通投资,全息技术在能源交通领域的创新应用
  • Spring 框架基础教程(Day03)
  • JVM 01
  • C++菜鸟教程 - 从入门到精通 第五节
  • 秦洪看盘|小市值股领涨,A股交易情绪复苏
  • 广西隆林突发山洪,致3人遇难1人失联
  • 光速晋级!2025年多哈世乒赛孙颖莎4比0战胜对手
  • 机器人为啥热衷“搞体育”,经济日报:是向加速融入日常生活发起的冲锋
  • 俄乌谈判开始
  • “16+8”“生酮饮食”,网红减肥法究竟靠谱吗?