fastjson中出现内存泄漏如何解决

fastjson中出现内存泄漏如何解决,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

关键代码:

publicclassFastJsonUtil{
/*
*将pojo对象转为json字符串,并且驼峰命名修改为下划线命名
*/
publicstaticStringbuildData(Objectbean){
try{
SerializeConfigconfig=newSerializeConfig();
config.propertyNamingStrategy=PropertyNamingStrategy.SnakeCase;
returnJSON.toJSONString(bean,config);
}catch(Exceptione){
returnnull;
}
}
}
//随意的一个实体类
publicclassT{
publicStringuserName;
publicStringuserArg;
publicStringuserGender;
publicStringuserAddr;

publicT(StringuserName,StringuserArg,StringuserGender,StringuserAddr){
this.userName=userName;
this.userArg=userArg;
this.userGender=userGender;
this.userAddr=userAddr;
}
}

模拟并发 (也可以使用 JMeter)

publicclassMain{
publicstaticvoidmain(String[]args)throwsInterruptedException{
ThreadPoolExecutorpoolExecutor=newThreadPoolExecutor(6,6,1,TimeUnit.SECONDS,newArrayBlockingQueue<>(999999));
for(;;){
//每隔1毫秒提交一次任务
TimeUnit.MILLISECONDS.sleep(1);
poolExecutor.submit(Main::prcess);
}
}

//模拟一个请求调用,最终将一个t对象转为json格式返回
publicstaticvoidprcess(){
Tt=newT("1","2","3","4");
Stringres=FastJsonUtil.buildData(t);
//System.out.printf(res);
}
}

jvm 启动参数:

-Xms4g -Xmx4g -Xmn2g -Xss1024K -XX:+UseConcMarkSweepGC(限制堆内存大小、新时代区与老年代的比例调节为1:1、设置线程栈大小、以及使用 cms垃圾回收器)

运行一段时间,会发现该 java 程序占用的内存远远大于 4g。

由于已经设置的堆内存大小已经限制为 4g 以内,然而 程序占用内存远超过 4g,直接往物理内存攀升,那么可能是堆外内存(直接内存或者是 metaspace 的内存)的泄漏导致的。

在该程序代码中,并没有对直接内存的操作,没有使用 netty 等与 io 相关的框架。

为了更加精确定位是直接内存还是 metaspace,追加一个启动参数 -XX:MaxDirectMemorySize=1g,重启项目,发现没有任何效果,问题依旧存在。

所以将问题锁定到 matespace 上,使用 jmap -heap pid 查看堆使用情况:

可以发现其中的 MaxMetaspaceSize 为系统内存大小,没有收到任何限制。所以可能是因为调用 fastjson 的某个方法后,它处理了一些某些事情,需要将某些东西存到 metaspace 中,由于 metaspace 没有限制内存大小,导致 java 程序占用内存情况超过 4g,不断攀升,最终会引发内存预警。

尝试着在 jvm 启动参数加上 -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m(一般来说这两个值可以设置为一样,不再赘述),再次启动项目,模拟并发,发现占用内存情况一切正常,没有超过限制值的情况出现。

所以,到这一步,可以判断出是由于 fastjson 在处理的时候可能一直加载了某个 class,导致 metaspace 内存占用过大。

在启动参数加上 -verbose:class 后再次启动项目,观察重复加载了哪个 class

[Loadedcom.alibaba.fastjson.serializer.ASMSerializer_1_Tfromfile:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar]
[Loadedcom.alibaba.fastjson.serializer.ASMSerializer_1_Tfromfile:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar]
[Loadedcom.alibaba.fastjson.serializer.ASMSerializer_1_Tfromfile:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar]
[Loadedcom.alibaba.fastjson.serializer.ASMSerializer_1_Tfromfile:/E:/maven/repository/com/alibaba/fastjson/1.2.29/fastjson-1.2.29.jar]
...

综上,显然可以得到结论,一直在加载的是 ASMSerializer_1_T 这个 class。

接下来就是查找 fastjson 在哪里一直重复加载了这个 class。

从 JSON.toJSONString 方法开始入手,进入到 com.alibaba.fastjson.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson.serializer.SerializeConfig, com.alibaba.fastjson.serializer.SerializeFilter[], java.lang.String, int, com.alibaba.fastjson.serializer.SerializerFeature...) 方法中

再依次进入 getObjectWriter(clazz) -> config.getObjectWriter(clazz) -> put(clazz, createJavaBeanSerializer(clazz)) -> createJavaBeanSerializer(beanInfo) -> createASMSerializer(beanInfo) 方法中

在 createASMSerializer 中,有这样几行代码

......
//拼接类名
StringclassName="ASMSerializer_"+seed.incrementAndGet()+"_"+clazz.getSimpleName();
StringpackageName=ASMSerializerFactory.class.getPackage().getName();
StringclassNameType=packageName.replace('.','/')+"/"+className;
StringclassNameFull=packageName+"."+className;
ClassWritercw=newClassWriter();
//然后这里就加载了ASMSerializer_的类
cw.visit(V1_5//
,ACC_PUBLIC+ACC_SUPER//
,classNameType//
,JavaBeanSerializer//
,newString[]{ObjectSerializer}//
);
......

所以,就是这里,每次调用到这里,就会 load ASMSerializer_1_T 到 metaspace 中。

而这部分代码在 ASMSerializerFactory 中。

回到用户代码,在 SerializeConfig config = new SerializeConfig() 这一行,进入 SerializeConfig 的无参构造,会调用它的有参构造。有参构造中有这么几行代码

if(asm){
asmFactory=newASMSerializerFactory();
}

一切都很清晰了,由于一直创建 SerializeConfig,导致 ASMSerializerFactory 也会被重复创建,之后 ASMSerializerFactory 再调用 本类中的 createASMSerializer 方法的时候,就会导致重复加载 com.alibaba.fastjson.serializer.ASMSerializer_1_T

解决办法:

SerializeConfig config = new SerializeConfig();config.propertyNamingStrategy = PropertyNamingStrategy.SnakeCase; 这两行代码提到方法外面,改造如下:

publicclassFastJsonUtil{
privatefinalstaticSerializeConfigconfig=newSerializeConfig();
static{
config.propertyNamingStrategy=PropertyNamingStrategy.SnakeCase;
}
/*
*将pojo对象转为json字符串,并且驼峰命名修改为下划线命名
*/
publicstaticStringbuildData(Objectbean){
try{
returnJSON.toJSONString(bean,config);
}catch(Exceptione){
returnnull;
}
}
}

重启项目,问题解决,不会再一直加载 ASMSerializer_1_T 这个类了。

使用 JVisualVm 观察内存活动情况

修改前的 metaspace:

fastjson中出现内存泄漏如何解决

fastjson中出现内存泄漏如何解决

修改后的 metaspace:

fastjson中出现内存泄漏如何解决

fastjson中出现内存泄漏如何解决

关于fastjson中出现内存泄漏如何解决问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注恰卡编程网行业资讯频道了解更多相关知识。

发布于 2021-07-29 22:00:23
收藏
分享
海报
0 条评论
199
上一篇:Docker中怎么部署MySQL8集群 下一篇:tomcat中出现内存泄露如何解决
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码