java 内存异常上升_JVM堆外内存异常增长的解决过程

其实有了MAT这类工具,一般堆内内存基本都能借助工具分析出大概问题所在,但堆外内存有时就不能直观地发现问题了,从解决过几次线上问题的现象总结,堆外内存过高80%都是这两种因素引起:

若metaspace正常,有可能是线程数过多造成的

若metaspace异常,有可能是classLoader过多造成的

当然了,并不是说只有这两种情况,有些也可能是直接内存泄露的问题,但如果你的项目不是大量操作直接内存,或者使用netty等第三方框架的话,可以考虑以上两个问题。

本次文章主要分享metaspace异常的解决过程。前段时间发现某个服务每隔几天就重启一次,由于使用k8s集群,进程内存达到某个阀值会被kill掉重启,通过jvm监控发现被kill之前的堆内内存十分正常,而metaspace却缓慢地上升:

8e31c3b69f4bb4b970b3d4713ffa99e0.png

然后通过jmap命令将内存dump出来后,利用mat工具打开,发现其classLoader数量特别多:

f8276788106493ea17770d8350dfc74e.png

6575442f223a3355d85c6d0234b692c8.png

PS:由于这里只列出重复类,所以数量没有对上

通过查看该classLoader的gc roots可以看出其引用路径:

a73b02ea7e18a6900f4acd299e74bca8.png

说实话,一开始看到groovyClassLoader有点懵,想不到哪里用到groovy,猜测是第三方依赖,于是通过maven查看依赖树,发现是jsonPath引进来的org.codebaus.groovy2.4.5,而服务使用其解析json。然后下载了groovy2.4.5的源码,根据上图的gc roots查看相关代码,然后发现org.codehaus.groovy.reflection.GroovyClassValuePreJava7的类注释:

/** Approximation of Java 7"s {@linkjava.lang.ClassValue} that works on earlier versions of Java.* Note that this implementation isn"t as good at Java 7"s; it doesn"t allow for some GC"ing that Java 7 would allow.* But, it"s good enough for our use.*/

重点在于“it doesn"t allow for some GC"ing that Java 7 would allow”,也就是说它有可能无法gc!但我明明使用的是java8呀,为何还用GroovyClassValuePreJava7呢?只好查看其引用位置,发现是通过org.codehaus.groovy.reflection.GroovyClassValueFactory#createGroovyClassValue创建的,通过系统参数groovy.use.classvalue=true/false控制使用GroovyClassValuePreJava7或GroovyClassValueJava7,默认前者。于是我尝试设置groovy.use.classvalue=true,服务运行24小时后重新dump出文件:

6c420f7781f3aa52e202fd0f3e6daa89.png

只有19个,证明classLoader成功释放!既然默认的有这个问题,为何它要这样设置呢?GroovyClassValueFactory的注释可以说明原因:

/*** This flag is introduced as a (hopefully) temporary workaround for a JVM bug, that is to say that using* ClassValue prevents the classes and classloaders from being unloaded.* See https://bugs.openjdk.java.net/browse/JDK-8136353*/

看来是为了解决ClassValue的问题,于是GroovyClassValuePreJava7自己实现了类似ClassValue的功能,GroovyClassValuePreJava7注释:

/** Approximation of Java 7"s {@linkjava.lang.ClassValue} that works on earlier versions of Java.* Note that this implementation isn"t as good at Java 7"s; it doesn"t allow for some GC"ing that Java 7 would allow.* But, it"s good enough for our use.*/

咳咳,问题你自己也没解决这个问题呀(-_-|||),而且貌似只有2.4才有这个问题,2.5开始GroovyClassValueFactory就默认使用GroovyClassValueJava7了(低版本跟泄露原因有关,且看下面)。

至此,问题的根源已经找到,我的做法是将groovy升级到2.5,目前服务正常运行。

而另一次由于线程数过高而导致堆外内存异常的情况,是有人配了server.tomcat.min-spare-threads=500,并且没有指定栈空间大小,按java官网的描述linux默认1M,这就导致堆外内存过大的问题

7f4c102aa912b37c69ccb78b3609ddc7.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值