18. HashMap的key典型错误使用姿势

一灰灰blogJava问题记录Map约 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

简单的测试下上面方法,发现过滤逻辑一直都没有生效

demo
demo

HashMap根据Key获取Value的方式,主要是根据key的hashcode去定位对应的元素位置,然后通过equals方法判断找到的对象是不是我们预期的目标

因为我们最上面的ImmutablePair类,没有覆盖这两个方法,所以是默认的,这个时候equals方法和==是等效的,主要是判断是否为同一个引用,所以上面的key每次都是重新创建对象,当然和缓存的不一致,从而导致每次都不命中,一直往Map里面塞数据,但是又回收不了,所以导致了这个问题

2. 小结

  • 对于HashMap的key对象,务必保证是重写了equalshashcode方法的
  • 用内存做缓存时,使用guava的cache并设置上限,相对而言是更加优雅的方式
  • 使用HashMap时,尽量指定Map的初始化容量,否则可能出现频繁的扩容;其次就是最好能保证下HashMap的个数,毫无限制的情况下,说不准哪天就暴雷了
Loading...