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,生产压测后恢复正常,问题解决
压测前:
压测后:
反思:
使用开源框架的时候,需及时更新新版本,避免已知问题