Java 8 构造函数引用的可怕性能和大堆占用空间?

我只是在我们的生产环境中有过一次相当不愉快的经历,导致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);
      }
    }
  }
}

运行 with 方法将导致该方法在索引命中 30k 后立即失败。随着程序不会失败,但不断冲击直到杀死(索引很容易达到1.5百万)。newMethod=true;OutOfMemoryErrornewMethod=false;

为什么在堆上创建如此多的元素,以至于它产生的速度如此之快?ArrayList::newObject[]OutOfMemoryError

(顺便说一句 - 当集合类型为 .)HashSet


答案 1

在第一种情况下 (),您使用的是采用初始容量参数的构造函数,而在第二种情况下,您不是。较大的初始容量(在代码中)会导致分配较大的容量,从而导致您的 s。ArrayList::newindexObject[]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;
}

类似的事情发生在 中,只是数组在被调用之前不会被分配。HashSetadd


答案 2

签名如下:computeIfAbsent

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

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

Function<Integer, List<Integer>> mappingFunction

当您在必要的地方编写时,编译器会查找合适的构造函数,即:ArrayList::newFunction<Integer, List<Integer>>

public ArrayList(int initialCapacity)

所以本质上你的代码相当于

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

并且您的键被视为值,这会导致预分配大小不断增加的数组,当然,这很快就会导致 .initialCapacityOutOfMemoryError

在此特定情况下,构造函数引用不合适。请改用 lambdas。如果用在 中,那么将是合适的。Supplier<? extends V>computeIfAbsentArrayList::new