可怕的表现& Java 8构造函数引用的大堆足迹?

时间:2016-02-09 16:10:15

标签: java constructor java-8 out-of-memory 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);
      }
    }
  }
}

使用newMethod=true;运行方法会导致方法在索引达到30k后立即失败OutOfMemoryError。使用newMethod=false;程序不会失败,但会一直冲击直到被杀(索引容易达到150万)。

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

(顺便说一下 - 当集合类型为HashSet时也会发生。)

2 个答案:

答案 0 :(得分:93)

在第一种情况下(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之前未分配数组。

答案 1 :(得分:76)

computeIfAbsent签名如下:

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

因此mappingFunction接收一个参数的函数。在您的情况K = IntegerV = 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

在这种特殊情况下,构造函数引用不合适。请改用lambdas。如果在Supplier<? extends V>中使用了computeIfAbsent,那么ArrayList::new就是合适的。