zoukankan      html  css  js  c++  java
  • fastjson反序列化使用不当导致内存泄露

    分析一个线上内存告警的问题时,发现了造成内存告警的原因是使用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转换包含date类型属性的对象时报错com.alibaba.fastjson.JSONException: For input string: "13:02:19"
    HTTP Status 500
    org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/json;charset=UTF-8' not supported
    myeclipse 报错:Set project compiler compliance settings to '1.5'
    Myeclipse安装svn插件
    junit单元测试报错Failed to load ApplicationContext,但是项目发布到tomcat浏览器访问没问题
    使用mybatis时,sql查出来的结果映射不进逆向工程生成的该模型
    整合mybatis时报错:Configuration problem: Unable to locate Spring NamespaceHandler for XML schema namespace [http://www.springframework.org/schema/tx]
    TinkerPop中的遍历:图的遍历步骤(3/3)
    TinkerPop中的遍历:图的遍历步骤(2/3)
  • 原文地址:https://www.cnblogs.com/liqipeng/p/11665889.html
Copyright © 2011-2022 走看看