分析一个线上内存告警的问题时,发现了造成内存告警的原因是使用fastjson不当导致的。

分析dump发现com.alibaba.fastjson.util.IdentityHashMap$Entry对象比较多。

查找相关文档

  1. fastjson IdentityHashMap 内存泄漏排查 (这篇文档分析描述的情况与我们遇到的问题的原因一样,是使用com.alibaba.fastjson.util.ParameterizedTypeImpl不当导致的)
  2. fastjon官方在很早的版本就修复过类似的问题,https://github.com/alibaba/fastjson/issues/849 ,相关代码:https://github.com/alibaba/fastjson/commit/ef50a5b756a6cab1ab753f4a661bdfb0ccbd6b7e ,他们修复的这个bug是针对com.alibaba.fastjson.TypeReference,这个类实际也是基于com.alibaba.fastjson.util.ParameterizedTypeImpl的。

问题产生的原因分析

  1. com.alibaba.fastjson.ParserConfig定义一个字段用于缓存不同类的反序列化器,使用的是IdentityHashMap(IdentityHashMap使用的是==比较key的值,不同于HashMap使用equals比较),缓存是以Type为key:
    private final IdentityHashMap<Type, ObjectDeserializer> deserializers = new IdentityHashMap<Type, ObjectDeserializer>();
  2. 而我们的业务代码是在调用一个接口后将结果反序列化,然后每次都去创建一个ParameterizedTypeImpl实例,而fastjson针对每次创建的PamrameterizedTypeImpl都会作为一个key加入到deserizers中进行缓存。
    // ... ...
    ParameterizedTypeImpl type = new ParameterizedTYpeImpl(new Type[]{ SomeInfo.class }, null, CommonVO.class);
    CommonVO<SomeInfo> result = (CommonVO<SomeInfo>)JSON.parseObject(jsonString, type);

    所以,随着不断的请求发起,内存泄漏产生了。(上面提到的fastjson自身的bug修复就是针对不同的类型又采用了ConcurrentHashMap基于Class进行了一次缓存)

问题修复

方法一:

由于这里主要只是因为泛型才用了ParameterizedTypeImp,并且只有这一处,所以可以简单粗暴把这个定义为局部变量的type改为private static final的全局变量就可以避免内存泄漏了

private static final ParameterizedTypeImpl SOME_INFO_TYPE = ...

方法二:

使用com.alibaba.fastjson.TypeReference。

JSON.parseObject(json, new TypeReference<CommonVO<T>>(SomeInfo.class) {});

https://github.com/alibaba/fastjson/wiki/TypeReference

问题模拟重现

代码:

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.ParameterizedTypeImpl; import java.lang.reflect.Type;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong; public class LeakDemo { public static void main(String[] args) {
/*
-Xms30m
-Xmx30m
-XX:+PrintGCDateStamps
-XX:+PrintGCDetails
-XX:+PrintHeapAtGC
-XX:+PrintGCApplicationStoppedTime
-Xloggc:/tmp/gc_%p_%t_.log
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/
*/
final long start = System.currentTimeMillis();
final AtomicLong counter = new AtomicLong(0);
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("count: " + counter.get());
System.out.println("took " + (System.currentTimeMillis() - start) + " ms");
}
})); SomeInfo someInfo = new SomeInfo();
someInfo.setName("Tom");
CommonVO<SomeInfo> result = new CommonVO<>();
result.setData(someInfo);
result.setRetCode(0);
result.setMessage("Success"); String json = JSON.toJSONString(result); // 模拟业务中不断的接口请求处理
while (true) {
ParameterizedTypeImpl type = new ParameterizedTypeImpl(new Type[]{SomeInfo.class}, null, CommonVO.class);
CommonVO<SomeInfo> tmpResult = (CommonVO<SomeInfo>) JSON.parseObject(json, type);
Objects.requireNonNull(tmpResult); counter.incrementAndGet();
}
} public static class CommonVO<T> {
private int retCode;
private String message;
private T data; public int getRetCode() {
return retCode;
} public void setRetCode(int retCode) {
this.retCode = retCode;
} public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} public T getData() {
return data;
} public void setData(T data) {
this.data = data;
}
} public static class SomeInfo {
private String name; public String getName() {
return name;
} public void setName(String name) {
this.name = name;
}
} }

执行结果:

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to /tmp/java_pid13092.hprof ...
Heap dump file created [48333772 bytes in 0.402 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.zip.ZipCoder.getBytes(ZipCoder.java:80)
at java.util.zip.ZipFile.getEntry(ZipFile.java:306)
at java.util.jar.JarFile.getEntry(JarFile.java:227)
at java.util.jar.JarFile.getJarEntry(JarFile.java:210)
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:840)
at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:818)
at sun.misc.URLClassPath$1.next(URLClassPath.java:226)
at sun.misc.URLClassPath$1.hasMoreElements(URLClassPath.java:236)
at java.net.URLClassLoader$3$1.run(URLClassLoader.java:583)
at java.net.URLClassLoader$3$1.run(URLClassLoader.java:581)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader$3.next(URLClassLoader.java:580)
at java.net.URLClassLoader$3.hasMoreElements(URLClassLoader.java:605)
at sun.misc.CompoundEnumeration.next(CompoundEnumeration.java:45)
at sun.misc.CompoundEnumeration.hasMoreElements(CompoundEnumeration.java:54)
at com.alibaba.fastjson.util.ServiceLoader.load(ServiceLoader.java:34)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:468)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:363)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:639)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:350)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:318)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:281)
at LeakDemo.main(LeakDemo.java:45)
count: 17300
took 6332 ms

fastjson反序列化使用不当导致内存泄露的更多相关文章

  1. Andorid 内存溢出与内存泄露,几种常见导致内存泄露的写法

    内存泄露,大部分是因为程序的逻辑不严谨,但是又可以跑通顺,然后导致的,内存溢出不会报错,如果不看日志信息是并不知道有泄露的.但是如果一直泄露,然后最终导致的内存溢出,仍然会使程序挂掉.内存溢出大部分是 ...

  2. MPMoviePlayerController导致statusBar消失,导致内存泄露leak

    1.MPMoviePlayerController使statusBar消失 同事写项目时,运行程序总导致statusBar状态条消失,然后就是界面会上移20个像素,导致最下面空白界面,找原因一直不知道 ...

  3. ThreadLocal是否会导致内存泄露

    什么是内存泄露? 维基百科的定义:[内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存],我的理解就是程序失去了对某段内存的控制,那么这段内存就算是泄露了. ThreadLocal为什么会导致 ...

  4. Android开发 静态static类与static方法持有Context是否导致内存泄露的疑问

    简述 在Android开发的过程中,难免会使用单例模式或者静态方法工具类.我们会让它们持有一些外部的Context或者View一般有以下几种情况: 单例模式,类的全局变量持有Context 或 Vie ...

  5. 一个很初级的错误 Destructor忘记override导致内存泄露

      TxxObj= class    public     Destructor Destroy(); override;!!!此处若无override,将导致内存泄露     end; Destru ...

  6. [C++11]shared_ptr循环引用导致内存泄露

    1 /* 2 * shared_ptr循环引用导致内存泄露 3 */ 4 5 struct A 6 { 7 shared_ptr<A> ptr; // 改为weak_ptr<A> ...

  7. ThreadLocal源码分析以及why导致内存泄露

    1 ThreadLocal? This class provides thread-local variables. These variables differ from their normal ...

  8. Handler导致内存泄露分析

    (非静态)内部类引起内存泄漏的原因         内部类的实现其实是通过编译器的语法糖(Syntactic sugar)实现的,通过生成相应的子类即以OutClassName$InteriorCla ...

  9. BingMap频繁Add Pushpin和Delete Pushpin会导致内存泄露

    近期在做性能測试的时候发现BingMap内存泄露(memory leak)的问题,查找了一些国外的帖子,发现也有类似的问题,可是没有好的解决的方法. https://social.msdn.micro ...

随机推荐

  1. 利用Python进行数据分析【第二版】【高清中文版英文版源代码】

    如果被河蟹请回复我更新链接   这是我花钱弄的,免费分享给大家.没有密码,直接可以观看!   希望大家不要拿去后再做收费分享   如果好用,请给个赞好嘛~~~   1.中文pdf 链接:https:/ ...

  2. GoCN每日新闻(2019-10-17)

    GoCN每日新闻(2019-10-17) 通过go module管理go tool https://marcofranssen.nl/manage-go-tools-via-go-modules/ 使 ...

  3. python 操作redis,存取为字节格式,避免转码加

    这种情况连接数据库,对数据的存取都是字节类型,存取时还得转码一下 from redis import Redis # 实例化redis对象 rdb = Redis(host='localhost', ...

  4. Server 2003 操作系统位数

    安装好电脑系统,如何查看windows 2003/xp/win7是64位还是32位? 方法/步骤 第一种方法:桌面上鼠标右键单击“计算机”(我的电脑) 在弹出的快捷菜单中选择“属性”,如果看到64的字 ...

  5. 解决:E: Could not get lock /var/lib/dpkg/lock

    问题: ubuntu16 执行 sudo apt install aria2 命令时,提示如下错误 E: Could not get lock /var/lib/dpkg/lock - open (1 ...

  6. 【软工实践】Beta冲刺(3/5)

    链接部分 队名:女生都队 组长博客: 博客链接 作业博客:博客链接 小组内容 恩泽(组长) 过去两天完成了哪些任务 描述 新增数据分析展示等功能API 服务器后端部署,API接口的beta版实现 展示 ...

  7. [源码分析]LinkedHashMap

    一个键有序的 HashMap   可以将 LinkedHashMap 理解为 LinkList + HashMap,所以研究LinkedHashMap之前要先看HashMap代码.这里不再赘述.其实L ...

  8. maven依赖 dependency中scope=compile 和 provided区别

    问题再现 上次这边朋友问我一个问题,就是他们在pom.xml中的dependency中,看到有一些是provided的情况,比如如下: <dependency> <groupId&g ...

  9. PyCharm虚拟环(Project Interpreter)手动安装第三方包图解教程

    PyCharm虚拟环(Project Interpreter)手动安装第三方包图解教程 an鑫_wolfxin2010 关注 2018.03.13 21:58* 字数 313 阅读 3782评论 1喜 ...

  10. JS高级:面向对象的构造函数

    1 创建对象的方式 1.1 字面量的方式创建对象 var p1 = { name: '张三', run: function () { console.log(this.name + '跑'); } } ...