01.29 同事反饋搜索系統不正常,所有接口響應都很慢,並且會超時
上應用監控平台看了下機器的數據
發現每台機器老年代內存占用比較高,一直在old gc,但是沒有回收到內存,所以一直STW,導致接口反應極慢。
看了下發布記錄,最近一次發布是在1-10號,排除是發布新版本的問題
嘗試重啟機器,恢復正常
問題是恢復了,但是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對象比較多
搜了下代碼引用
排查搜索組寫的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文件:
從GC-Root上來看,ParserConfig占比了絕大部分.跟蹤了代碼,發現了問題所在
ParserConfig.class
IdentityHashMap.class
System.class
翻譯一下:
無論給定的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,生產壓測后恢復正常,問題解決
壓測前:
壓測后:
反思:
使用開源框架的時候,需及時更新新版本,避免已知問題