I just had a rather unpleasant experience in our production environment, causing OutOfMemoryErrors: heapspace..
I traced the issue to my use of ArrayList::new
in a function.
To verify that this is actually performing worse than normal creation via a declared constructor (t -> new ArrayList<>()
), I wrote the following small method:
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);
}
}
}
}
Running the method with newMethod=true;
will cause the method to fail with OutOfMemoryError
just after index hits 30k. With newMethod=false;
the program does not fail, but keeps pounding away until killed (index easily reaches 1.5 milion).
Why does ArrayList::new
create so many Object[]
elements on the heap that it causes OutOfMemoryError
so fast?
(By the way - it also happens when the collection type is HashSet
.)
The
computeIfAbsent
signature is the following:So the
mappingFunction
is the function which receives one argument. In your caseK = Integer
andV = List<Integer>
, so the signature becomes (omitting PECS):When you write
ArrayList::new
in the place whereFunction<Integer, List<Integer>>
is necessary, compiler looks for the suitable constructor which is:So essentially your code is equivalent to
And your keys are treated as
initialCapacity
values which leads to pre-allocation of arrays of ever increasing size, which, of course, quite fast leads toOutOfMemoryError
.In this particular case constructor references are not suitable. Use lambdas instead. Were the
Supplier<? extends V>
used incomputeIfAbsent
, thenArrayList::new
would be appropriate.In the first case (
ArrayList::new
) you are using the constructor which takes an initial capacity argument, in the second case you are not. A large initial capacity (index
in your code) causes a largeObject[]
to be allocated, resulting in yourOutOfMemoryError
s.Here are the two constructors' current implementations:
Something similar happens in
HashSet
, except the array is not allocated untiladd
is called.