群里说生产环境上遇到了 OOM,并发出了截图…
起因
事情的起因是这样,群里有个小伙伴发了个消息:
代码部分的截图为:
分析
一杯茶、一包烟,一行代码看一天
乍一看这一行,能猜测出来 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:
-
Supplier
Supplier<List<String>> supplier = ArrayList::new; //等同于 Supplier<List<String>> supplier = new Supplier<List<String>>() { @Override public List<String> get() { //调用无参构造函数 return new ArrayList<>(); } };
-
Function
Function<Integer, List<String>> function = ArrayList::new; //等同于 Function<Integer, List<String>> function = new Function<Integer, List<String>>() { @Override public List<String> apply(Integer integer) { //调用设置初始空间大小的构造函数 return new ArrayList<>(integer); } };
开发者用了第二种 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)