And*_*s K 107 java constructor out-of-memory java-8 method-reference
我刚刚在我们的生产环境中遇到了相当不愉快的经历 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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
运行方法newMethod=true;
将导致方法OutOfMemoryError
在索引达到30k后失败.随着newMethod=false;
程序不会失败,但一直冲击直到被杀(索引容易达到150万).
为什么在堆上ArrayList::new
创建如此多的Object[]
元素会导致OutOfMemoryError
如此之快?
(顺便说一下 - 当集合类型出现时也会发生HashSet
.)
Ale*_*lex 94
在第一种情况(ArrayList::new
)中,您使用的是带有初始容量参数的构造函数,在第二种情况下,您不是.较大的初始容量(index
在您的代码中)导致Object[]
分配大量,导致您的OutOfMemoryError
s.
以下是两个构造函数的当前实现:
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;
}
Run Code Online (Sandbox Code Playgroud)
发生了类似的事情HashSet
,除了在add
调用之前不分配数组.
Tag*_*eev 77
该computeIfAbsent
签名如下:
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
Run Code Online (Sandbox Code Playgroud)
所以这mappingFunction
是接收一个参数的函数.在你的情况K = Integer
和V = List<Integer>
,所以它的签名变成(省略PECS):
Function<Integer, List<Integer>> mappingFunction
Run Code Online (Sandbox Code Playgroud)
当你ArrayList::new
在Function<Integer, List<Integer>>
必要的地方写,编译器寻找合适的构造函数,它是:
public ArrayList(int initialCapacity)
Run Code Online (Sandbox Code Playgroud)
所以基本上你的代码相当于
map.computeIfAbsent(index, i->new ArrayList<>(i)).add(index);
Run Code Online (Sandbox Code Playgroud)
并且您的密钥被视为initialCapacity
值,这导致预先分配不断增加的大小的数组,当然,这很快就会导致OutOfMemoryError
.
在这种特殊情况下,构造函数引用不合适.请改用lambdas.如果Supplier<? extends V>
用过computeIfAbsent
,则ArrayList::new
适当.