记一次JVM内存溢出解决过程
1.问题简述
线上的服务器随着访问量的增加莫名其妙的挂了查看了一下错误日志:
java.lang.OutOfMemoryError: Compressed class space
2.问题分析
在Java8以前,有一个选项是UseCompressedOops。所谓OOPS是指“ordinary object pointers“,就是原始指针。Java Runtime可以用这个指针直接访问指针对应的内存,做相应的操作(比如发起GC时做copy and sweep)。
那么Compressed是啥意思?64bit的JVM出现后,OOPS的尺寸也变成了64bit,比之前的大了一倍。这会引入性能损耗——占的内存double了,并且同尺寸的CPU Cache要少存一倍的OOPS。
于是就有了UseCompressedOops这个选项。打开后,OOPS变成了32bit。但32bit的base是8,所以能引用的空间是32GB——这远大于目前经常给jvm进程内存分配的空间。
到了Java8,永久代被干掉了,有了“meta space”的概念,存储jvm中的元数据,包括byte code,class等信息。Java8在UseCompressedOops之外,额外增加了一个新选项叫做UseCompressedClassPointer。这个选项打开后,class信息中的指针也用32bit的Compressed版本。而这些指针指向的空间被称作“Compressed Class Space”。默认大小是1G,但可以通过“CompressedClassSpaceSize”调整。
如果你的java程序引用了太多的包,有可能会造成这个空间不够用,于是会看到
java.lang.OutOfMemoryError: Compressed class space
既然是空间不够用了那么是不是调大CompreseedClassSpaceSize就可以了.
3.解决问题
3.1设置参数
JVM设置了两个选项可以调节CompreseedClassSpaceSize大小:
-XX:+UseCompressedClassPointers(压缩开关)
-XX:CompressedClassSpaceSize(Compressed Class Space 空间大小限制)。
jvm配置参数设置为:
JAVA_OPTS="-server -Dfile.encoding=UTF-8 -Xms2g -Xmx4g -Xmn1g -Xss512K -verbose:gc -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=10 -XX:PermSize=1g -XX:MaxPermSize=1g -XX:CompressedClassSpaceSize=3g "
-XX:+UseCompressedClassPointers 是需要 -XX:+UseCompressedOops 开启的,所以堆大小要是大于 32G,CompressedOops 自动关闭,CompressedClassPointers 也会关闭的,关闭了就没有 Compressed Class Space 了,这块就是 Class Space 了。-XX:CompressedClassSpaceSize 大小的设置也是有限制,因为压缩开关是受制于 32G,所以这个自然也是不能大于 32G,不过 hotspot 规定了这个参数不准大于 3G,所以这个参数其实是不能大于 3G。
3.2 验证
使用
ps aux|grep java
查询出jvm进程的PID
使用
jcmd PID GC.heap_info
查看内存使用情况,可以看出来内存是不断快速增长的
为了更方便的查找问题,配置使用Java VisualVM 工具查看具体信息具体安装方式查看
安装VisualVM .
可以看出内存还是在不断的增长过程中,当达到设置的最大内存大小时候就会出现内存溢出。
3.3 分析
使用Java VisualVM工具生成堆dump文件,然后将文件下载到本地,使用mat软件分析内存泄露原因。
使用mat打开生成的dump文件,在overview视图下打开Leak Suspects选项,生成内存泄露分析。
可以看出内存泄露是由于 org.codehaus.groovy.reflection.ClassInfo 引起的.
打开idea的maven查找这个类发现是由ShardingJDBC使用的
使用JVM参数可以打印出执行过程中类的加载卸载过程
-XX:+TraceClassLoading 跟踪类的加载
-XX:+TraceClassUnloading 跟踪类的卸载
每次请求执行都会打印
Script1 from file:/groovy/shell
这里的分表表达式使用groovy进行动态编译,生成Java class info, 而其位于 方法区; 不停的生成对象, 触发 full gc, 而full gc并不能回收多少对象; 导致的现象就是, 内存不断增长。
3.4 解决
新版的Sharding jdbc使用了缓存, 而不是每次请求都使用 groovy动态编译,
有FGC问题的读写分离版本是 3.0.0.M1,升级成为3.1.0版本即可。
升级后可以看到内存不在持续增长。
本文地址:https://blog.csdn.net/TimeQuantum/article/details/109642421
下一篇: 【内部类、匿名内部类、API】的学习