问题概述

公司使用k8s部署服务,发现有个pod自动重启,查看监控发现重启前的内存使用基本快达到最大限制4G,推测是由于OOM导致的自动重启。
进一步查看监控,发现除了class storage占用比较大,其他都很正常。而这个class storage也确实大的有点不正常,最大居然达到了1G多,查看其他正常的服务,一般也就一两百M。

openj9和hotspot差异

我们都知道jvm加载的class信息是存储在方法区的。但是不同jvm对于方法区的实现都有比较大的差异,hotspot在java8以前叫永久代,java8以后叫元空间。
以hotspot java8为例,通过设置-XX:MetaspaceSize,元空间的大小超过这个值以后,就会触发full gc,先尝试进行class unload,然后才会尝试扩容。
但是openj9没有这个参数,如果使用默认的gc策略(Gencon),默认只有加载的class数量达到一定阈值才会触发class unload。而这里有个隐患就是,默认的这个值比较大(8万),等到达到这个阈值,class占用内存可能已经很大了。
虽然这个参数可以通过-Xgc:classUnloadingKickoffThreshold=进行设置,但是具体设置为多少合适,其实并不太好定(个人感觉是openj9一个比较坑的地方)。

参考文章:
https://github.com/eclipse-openj9/openj9/issues/8388
https://github.com/eclipse-openj9/openj9/issues/12021
https://www.eclipse.org/openj9/docs/xgc/#classunloadingkickoffthreshold

为什么会加载这么多class

很幸运的在开发环境重现问题接口。
由于openj9不支持-XX:+TraceClassLoading和-XX:+TraceClassUnloading参数,只能在本地进行测试,加上这两个参数后,控制台会输出class load和unload的日志。
重复执行有问题的接口,发现每次执行接口,都会有如下clas load信息:

[Loaded com.alibaba.fastjson.serializer.ASMSerializer_1_DecisionTreeVo from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_2_DecisionTree from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_3_ImportFactObjectType from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_4_VariableTreeNode from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_5_ConditionTreeNode from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_6_ActionTreeNode from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]
[Loaded com.alibaba.fastjson.serializer.ASMSerializer_7_VarCreate from file:/D:/data/soft/apache-maven-3.6.1/repository/com/alibaba/fastjson/1.2.75/fastjson-1.2.75.jar]

很明显,是fastjson的问题,正好在网上看到过类似问题
参考这篇文章:https://zhuanlan.zhihu.com/p/208527935

大致意思是说SerializeConfig创建时默认会创建一个ASM代理类用来实现对目标对象的序列化
在代码中搜索到SerializeConfig,定位到如下代码:

//其他代码略
	SerializeConfig config = new SerializeConfig();
    config.propertyNamingStrategy = PropertyNamingStrategy.KebabCase;
    return JSON.toJSONString(dto, config);

基本确定是每次new SerializeConfig()导致的
把SerializeConfig提升为静态变量,修改如下:

private static final SerializeConfig config = new SerializeConfig();
static {
 config.propertyNamingStrategy = PropertyNamingStrategy.KebabCase;
}

//其他代码略
return JSON.toJSONString(dto, config);

再进行测试,问题解决

思考

这次算是运气好,在本地重现发现了问题接口,但是如果是一个很复杂的服务,接口众多,想直接定位到问题接口,就比较难了
那么有没有比较好的办法,直接从线上环境排查到问题呢?

1、如果是hotspot,通过jmap -clstats pid命令,即可查看到类的加载情况,但是可惜openj9不支持
2、通过arthas的classloader命令,可以看出fastjson的ASMClassLoader加载了大量的class,能看出问题,但是局限于fastjson这种使用自定义classloader的情况
在这里插入图片描述
3、使用arthas sc命令:sc * > /tmp/sc_output
把加载的class输出到/tmp/sc_output文件,然后再进行分析

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐