Fastjson反序列化低版本使用ParameterizedTypeImpl導致內存泄露


01.29 同事反饋搜索系統不正常,所有接口響應都很慢,並且會超時

上應用監控平台看了下機器的數據

發現每台機器老年代內存占用比較高,一直在old gc,但是沒有回收到內存,所以一直STW,導致接口反應極慢。

看了下發布記錄,最近一次發布是在1-10號,排除是發布新版本的問題

嘗試重啟機器,恢復正常

image-20220222175454793

問題是恢復了,但是OOM的原因還沒有找到

使用dump命令下一個dump文件下來分析

jmap -dump:live,format=b,file=/dump2021.hprof pid

gzip -c dump2019.hprof > dump.gz

分析dump發現com.alibaba.fastjson.util.IdentityHashMap$Entry對象比較多

企業微信截圖_16443904106293

搜了下代碼引用

image-20220222191501995

排查搜索組寫的ES源碼發現,在查詢成功后直接返回非ApiResult封裝的對象時,每次解析都會new一個ParameterizedTypeImpl進行處理。

這個時候並不角色有什么問題,因為就算new一個對象出來,解析完也會被回收,不會走到老年代中於是本地跑了個方法嘗試重現這個問題

  /*
        -Xms30m
        -Xmx30m
        -XX:+PrintGCDateStamps
        -XX:+PrintGCDetails
        -XX:+PrintHeapAtGC
        -XX:+PrintGCApplicationStoppedTime
        -Xloggc:/tmp/gc_%p_%t_.log
        -XX:+HeapDumpOnOutOfMemoryError
        -XX:HeapDumpPath=/tmp/
         */public static void main(String[] args) {       final long start = System.currentTimeMillis();      final AtomicLong counter = new AtomicLong();
      Runtime.getRuntime().addShutdownHook(new Thread(() -> {
          System.out.println(counter.get());
          System.out.println(System.currentTimeMillis() - start);
      }));
       
      SomeInfo someInfo = new SomeInfo();
      someInfo.setName("jerry");
      EsBaseResult<SomeInfo> result = new EsBaseResult<>();
      result.setResult(someInfo);
      result.setCode(0);
      result.setMessage("Success");
      String json = JSON.toJSONString(result);

      while (true){
          EsBaseResult<SomeInfo> response = JSON.parseObject(json,
                  new ParameterizedTypeImpl(new Type[] { new ParameterizedTypeImpl(new Type[] { SomeInfo.class }, null, EsQueryResult.class) }, null, EsBaseResult.class));
          Objects.requireNonNull(response);
          counter.incrementAndGet();
      }
  }

  @Data
  public static class EsBaseResult<T> implements Serializable {

      private Integer           code;

      private String           message;

      private T                 result;

      public boolean isSuccess(){
          return code ==0;
      }

      public String getErrorCode(){
          return code+"";
      }
  }

  @Data
  public static class SomeInfo {
      private String name;
  }

不出意外,查詢7k次后OOM了

Exception in thread "main" com.alibaba.fastjson.JSONException: GC overhead limit exceeded
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:693)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:396)

COUNT: 7832
COST: 6273ms

查看dump文件:

image-20220222191522197

從GC-Root上來看,ParserConfig占比了絕大部分.跟蹤了代碼,發現了問題所在

ParserConfig.class

image-20220222191605638

IdentityHashMap.class

image-20220222191615914

System.class

image-20220222191627707

翻譯一下:

無論給定的x對象是否覆蓋了hashCode()方法,都會調用默認的hashCode()方法返回hashCode,如果x == null, 返回0。

這個默認的hashCode()方法就是Object類中的hashCode方法。

這說明默認的hashCode方法是根據對象的地址轉換所得到的。

每次new出來的新對象的地址都是不同,所以會出現這個問題

解決方案:

1.使用同一個對象,采用static對象引用

2.升級高版本fasjson,新版中已經修復了這個問題,原理是增加了一個cache存儲類型

https://github.com/alibaba/fastjson/commit/38070ca5a2713795403334c78f43470a560684b7

采用了方案2,生產壓測后恢復正常,問題解決

壓測前:

image-20220222191701038

壓測后:

image-20220222191647777

反思:
使用開源框架的時候,需及時更新新版本,避免已知問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM