Java 8构造函数参考的可怕的性能和大堆的足迹?

我只是在我们的生产环境中有一个相当不愉快的经历,导致OutOfMemoryErrors: heapspace..

我追溯到我的使用函数中ArrayList::new的问题。

为了validation这实际上是通过一个声明的构造函数( t -> new ArrayList<>() )执行比正常创build更差,我写了下面的小方法:

 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); } } } } 

newMethod=true;运行该方法newMethod=true; 只会在索引点击30k之后导致方法失败并返回OutOfMemoryError 。 用newMethod=false; 程序不失败,但一直在冲击着,直到被杀(索引容易达到150万)。

为什么ArrayList::new在堆上创build如此多的Object[]元素,导致OutOfMemoryError这么快?

(顺便说一下 – 当集合types是HashSet时也会发生。)

在第一种情况下( ArrayList::new ),你使用的构造函数采用初始容量参数,在第二种情况下,你不是。 初始容量较大(代码中的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之前不分配数组。

computeIfAbsent签名是以下内容:

 V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) 

所以mappingFunction接收一个参数的函数。 在你的情况下, K = IntegerV = List<Integer> ,所以签名变成(省略PECS):

 Function<Integer, List<Integer>> mappingFunction 

当你在需要Function<Integer, List<Integer>>的地方写ArrayList::new时,编译器寻找合适的构造函数:

 public ArrayList(int initialCapacity) 

所以基本上你的代码等同于

 map.computeIfAbsent(index, i->new ArrayList<>(i)).add(index); 

而且你的密钥被当作initialCapacity值来处理,这会导致预先分配不断增加的数组的数量,当然这些数组相当快就会导致OutOfMemoryError

在这个特殊情况下,构造函数的引用是不合适的。 使用lambdas代替。 Supplier<? extends V> Supplier<? extends V>computeIfAbsent使用的Supplier<? extends V> ,那么ArrayList::new将是适当的。