群里说生产环境上遇到了 OOM,并发出了截图…

起因

事情的起因是这样,群里有个小伙伴发了个消息:

20220429-1

代码部分的截图为:

20220429-2

分析

一杯茶、一包烟,一行代码看一天

乍一看这一行,能猜测出来 map 的 value 的泛型为 List,猜测开发同学的意思是如果 orderId 不存在,则会创建一个 ArrayList,思路是没错的,但就是在调用这个方法的时候出现了 OOM 。

看调用的这个方法为 computeIfAbsent,和我脑子里第一反应这个场景调用的方法 putIfAbsent 不太一样,所以看了下源码的定义,发现 computeIfAbsent 的第二个参数类型为 Function:

public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
    ....
}

看到这问题就很清晰了,原因就在代码的第二个参数 ArrayList::new 上,开发者认为第二个参数的 lambda 接口类型是 Supplier,结果是 Function,ArrayList::new 在 supplier 的时候调用无参构造函数,结果在泛型为 Integer 的 Function 下调用了 new ArrayList(int initialCapacity),同理也可以推断出 map 的 key 的泛型为 Integer。

下面列一下刚提到的两种 lambda:

开发者用了第二种 Function 的,那这个 apply 的参数 integer 是啥呢?很明显,是 map 那个 key sOrderVo.getOrderId(),相当于创建了初始大小为订单号大小的一个 ArrayList,ArrayList 里靠 Object[] elementData 来存储数据,如果订单号很大,相当于 new 了一个硕大的 Object 数组,那紫腚会 OOM 啊。这相当于被 lambda 给坑了一把,本来想创建默认大小的 ArrayList,结果在无意中创建了硕大的….属实无情。

简单复现一下:

Map<Integer, List<String>> map = new HashMap<>();
map.computeIfAbsent(999999999, ArrayList::new);
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.ArrayList.<init>(ArrayList.java:156)

结论

20220429-4

20220429-3