【SpringBoot项目OOM记录及处理】
SpringBoot项目OOM记录及处理
- 一、背景
- 二、临时处理措施
- 三、准备分析dump文件
- 四、堆外内存
- 五、sun.reflect.GeneratedMethod
- 六、处理方案
一、背景
- java 环境
java -version
openjdk version “1.8.0_412” - centos版本
cat /etc/centos-release
CentOS Linux release 7.8.2003 (Core) - 项目正常运行一段时间后,突然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文件
jmap -dump:live,format=b,file=heapdump3.hprof 1267
1267是服务PID,但是需要注意该命令会引起full GC,尽量避免生产环境少人的时候使用,主要分析hprof这个堆内存dump文件jstack 1267 > thread_dump.txt
,生成线程dump文件分析cpu居高不下的问题,但是本服务cpu正常,所以基本上不是线程问题- 使用jprofile进行分析heapdump3.hprof文件
- 但是堆内存很正常,如果不正常的话,可以通过选中占比最大的class,然后鼠标右键点击Use selected instance去分析引用该class的地方
- 查出是否堆内存泄漏问题,但是发现不是。
四、堆外内存
- 如果堆内存没有泄漏,那么就要考虑是否是堆外内存引起的问题
- 由于我设置了Xmx和Xms,但是服务还是超过了Xmx内存,且堆内存并没有明显泄漏的地方,基本可以确定就是堆外内存,也就是metaspace泄漏,在java1.8中,XX:MaxMetaspaceSize是没有上限的,所以如果metaspace泄漏,很容易oom
- 分析堆外内存
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.8后,Hotspot虚拟机已经移除了永久代,使用了元空间代替。」 由于我们线上使用的是JDK1.8,「所以我们对于元空间的最大容量根本就没有做限制」
- 在本地开发环境中加入对应参数
-XX:+TraceClassLoading -XX:+TraceClassUnloading -verbose:class -XX:MaxMetaspaceSize=100m
开始打印装载class的信息 - 然后就发现不停的打印
[Loaded sun.reflect.GeneratedMethodAccessor191 from __JVM_DefineClass__]
还有由于超过了MaxMetaspaceSize=100m导致的unloading信息 - 然后再去线上分析
jmap -clstats 1267
发现也有大量的sun.reflect.GeneratedMethod
相关class - 通过java 自带的visualVM分析metaspace空间
10.发现随着[Loaded sun.reflect.GeneratedMethodAccessor191 from __JVM_DefineClass__]
不断增长,metaspace也越来越大
五、sun.reflect.GeneratedMethod
- Java反射调用在多次执行后,JVM会生成字节码来提高性能,即GeneratedMethodAccessor和GeneratedConstructorAccessor。这些类会被加载到Metaspace,导致内存问题。默认15次
- 当使用Java反射时,Java虚拟机有两种方法获取被反射的类的信息。它可以使用一个JNI存取器;如果使用Java字节码存取器,则需要拥有它自己的Java类和类加载器(sun/reflect/GeneratedMethodAccessor类和sun/reflect/DelegatingClassLoader),这些类和类加载器使用本机内存。字节码存取器也可以被JIT编译,这样会增加本机内存的使用。如果Java反射被频繁使用,会显著地增加本机内存的使用。
- Java虚拟机会首先使用JNI存取器,然后在访问了同一个类若干次后,会改为使用Java字节码存取器。这种当Java虚拟机从JNI存取器改为字节码存取器的行为被称为膨胀(Inflation)。Inflation机制提高了反射的性能,但是对于重度使用反射的项目可能存在隐患,它带来了两个问题:(1)初次加载的性能损失;(2)动态加载的字节码导致PermGen持续增长。幸运的是,我们可以通过一个设置-Dsun.reflect.inflationThreshold=N控制这种行为,sun.reflect.inflationThreshold会告诉Java虚拟机使用JNI存取器多少次。
六、处理方案
-Dsun.reflect.inflationThreshold=2147483647
设置启动参数使得只使用JNI存取器- 升级到java17
- 完整启动参数
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 &
- 需要注意不能使用
-Dsun.reflect.noInflation=true
会导致直接生成字节码文件,而不使用JNI存取器