近期看到一篇文章,简单做个复盘(类似的问题,网上总结的文章也有很多,不喜勿喷)。
业务场景是需要统计后端应用接口访问量,代码的实现简单描述是基于接口的地址、名称等作为属性,包装成一个对象。类似如下:
public class Key {
private String url;
private String name;
public Key(String url, String name){
this.url = url;
this.name = name;
}
//省略get、set方法
}
采用ConcurrentHashMap作为存储容器,上述对象作为key,访问次数(atomic类型的值)作为value,来记录每个接口的访问量。代码中基于aop进行拦截,所有接口的请求信息均会保存在此。
结果代码上线不久后,内存就发生了oom,通过查看dump文件,发现ConcurrentHashMap中存储了上千万个对象。系统明明只有几百个接口,为什么实际内存中会存储上千万个呢?
原因就在于Key对象没有重写hashcode方法,于是每次请求进来时,尽管接口都是同一个,但new出来的Key对象的hashcode值是不同的(实际是基于地址进行计算的),所以存储在ConcurrentHashMap中时,被当成不同的key处理了,相当于没有去重。
最终将hashcode和equals方法进行重写,问题就得到解决了,内存中的每个key都只代表其中一个接口。类似的场景,我们在使用hashmap、hashset时,如果有需要对key进行去重的场景,且key的类型是java对象,一定要主意考虑是否需要重写hashcode和equals方法。
ps:对开源技术感兴趣的朋友,欢迎沟通交流。https://github.com/zhangxiaomin1993