18. HashMap的key典型错误使用姿势
约 708 字大约 2 分钟
记录一个非常低级的错误导致的java应用一直fullgc的问题;根本原因就是HashMap的key使用姿势不对
1. 问题记录
先捞出有问题的现场代码,之前写了一个简单的工具类,用来保存两个元素,简单的模拟了一下Guava的实现姿势
public final class ImmutablePair<L, R> {
@Getter
private final L left;
@Getter
private final R right;
private ImmutablePair(final L l, final R r) {
this.left = l;
this.right = r;
}
public static <L, R> ImmutablePair<L, R> of(L left, R right) {
return new ImmutablePair<>(left, right);
}
}
最开始主要是由于某些地方返回结果时,需要返回多个对象,而java并不能像python那么友好的支持这个功能,所以写了上面这个简单的工具类,对返回结果进行一个简单的封装
距离这个工具类写完之后一两个月的时间,突然有个临时需求场景,对于每次的请求,需要做一个简单的内存过滤;如果这次请求距离上次超过5s, 则直接不处理;否则才接受;于是写了下面这段代码
private Map<ImmutablePair<String, Integer>, Long> cache = new HashMap<>();
public String process(String k, Integer id) {
ImmutablePair key = ImmutablePair.of(k, id);
Long last = cache.get(key);
long now = System.currentTimeMillis();
if (last == null || now - last > 5000) {
cache.put(key, now);
return "new";
} else {
return null;
}
}
直接看上面这段代码,貌似没有啥问题,然后愉快的跑起来;但是一段时间之后呢?内存疯狂的上涨,且一直在fullgc
简单的测试下上面方法,发现过滤逻辑一直都没有生效
HashMap根据Key获取Value的方式,主要是根据key的hashcode
去定位对应的元素位置,然后通过equals
方法判断找到的对象是不是我们预期的目标
因为我们最上面的ImmutablePair
类,没有覆盖这两个方法,所以是默认的,这个时候equals
方法和==
是等效的,主要是判断是否为同一个引用,所以上面的key每次都是重新创建对象,当然和缓存的不一致,从而导致每次都不命中,一直往Map里面塞数据,但是又回收不了,所以导致了这个问题
2. 小结
- 对于HashMap的key对象,务必保证是重写了
equals
和hashcode
方法的 - 用内存做缓存时,使用guava的cache并设置上限,相对而言是更加优雅的方式
- 使用HashMap时,尽量指定Map的初始化容量,否则可能出现频繁的扩容;其次就是最好能保证下HashMap的个数,毫无限制的情况下,说不准哪天就暴雷了
Loading...