我只是在我们的生产环境中有一个相当不愉快的经历,导致了OutOfMemoryErrors: heapspace..。
我跟踪这个问题是因为我在一个函数中使用了ArrayList::new。
为了验证通过声明的构造函数(t -> new ArrayList<>())执行比正常创建更糟糕的操作,我编写了以下小方法:
public class TestMain {
public static void main(String[] args) {
boolean newMethod = false;
Map<Integer,List<Integer>> map = new HashMap<>();
int index = 0;
while(true){
if (newMethod) {
map.computeIfAbsent(index, ArrayList::new).add(index);
} else {
map.computeIfAbsent(index, i->new ArrayList<>()).add(index);
}
if (index++ % 100 == 0) {
System.out.println("Reached index "+index);
}
}
}
}在索引达到30k后,使用newMethod=true;运行该方法将导致OutOfMemoryError失败。使用newMethod=false;,该程序不会失败,但会一直执行到死亡(索引很容易达到1.5百万)。
为什么ArrayList::new会在堆中创建这么多的Object[]元素,以至于导致OutOfMemoryError的速度如此之快?
(顺便说一句,当集合类型为HashSet时也会发生这种情况。)
发布于 2016-02-09 16:21:33
在第一种情况(ArrayList::new)中,您使用的是接受初始容量参数的constructor,而在第二种情况下,则不是。较大的初始容量(代码中的index)会导致分配大量的Object[],从而导致OutOfMemoryError。
下面是两个构造函数的当前实现:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}在HashSet中也会发生类似的情况,除非在调用add之前不会分配数组。
发布于 2016-02-09 16:32:23
computeIfAbsent签名如下:
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)因此,mappingFunction是接收一个参数的函数。在您的例子中,K = Integer和V = List<Integer>,因此签名变成(省略PECS):
Function<Integer, List<Integer>> mappingFunction当您在需要使用ArrayList::new的地方编写Function<Integer, List<Integer>>时,编译器会查找合适的构造函数,即:
public ArrayList(int initialCapacity)所以从本质上说,您的代码相当于
map.computeIfAbsent(index, i->new ArrayList<>(i)).add(index);您的键被视为initialCapacity值,这会导致不断增加的数组的预分配,这当然会很快导致OutOfMemoryError。
在这种情况下,构造函数引用不合适。用兰巴斯代替。如果Supplier<? extends V>在computeIfAbsent中使用,那么ArrayList::new是合适的。
https://stackoverflow.com/questions/35296734
复制相似问题