Java 8構造函數參考的可怕表現和大堆堆空間?


Answers

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將是適當的。

Question

我在生產環境中遇到了相當不愉快的經歷,導致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;運行該方法newMethod=true; 在索引點擊30k之後,將導致該方法失敗並返回OutOfMemoryError 。 用newMethod=false; 該計劃不會失敗,但會一直衝擊到死亡(索引容易達到150萬)。

為什麼ArrayList::new在堆上創建如此多的Object[]元素,導致OutOfMemoryError如此之快?

(順便說一句 - 當集合類型是HashSet時也會發生。)