web-dev-qa-db-ja.com

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

_newMethod=true;_でメソッドを実行すると、インデックスが30kに達した直後にメソッドがOutOfMemoryErrorで失敗します。 _newMethod=false;_を使用すると、プログラムは失敗しませんが、強制終了されるまで激しく動き続けます(インデックスは簡単に1.5ミリオンに達します)。

_ArrayList::new_がヒープ上に非常に多くの_Object[]_要素を作成するので、OutOfMemoryErrorが非常に高速になるのはなぜですか?

(ところで、コレクションタイプがHashSetの場合にも発生します。)

107
Anders K

最初の場合(ArrayList::new)最初の容量引数を使用する constructor を使用していますが、2番目の場合は使用していません。大きな初期容量(コード内のindex)により、大きなObject[]が割り当てられ、OutOfMemoryErrorsになります。

以下は、2つのコンストラクターの現在の実装です。

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でも同様のことが起こります。

94
Alex

computeIfAbsent署名は次のとおりです。

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

したがって、mappingFunctionreceives引数を1つ持つ関数です。あなたの場合K = IntegerおよびV = List<Integer>、したがって、署名は(PECSを省略)になります。

Function<Integer, List<Integer>> mappingFunction

ArrayList::newFunction<Integer, List<Integer>>が必要です。コンパイラは次の適切なコンストラクタを探します。

public ArrayList(int initialCapacity)

したがって、本質的にあなたのコードは

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

そして、キーはinitialCapacity値として扱われ、サイズが増え続ける配列の事前割り当てにつながります。もちろん、これは非常に高速でOutOfMemoryErrorにつながります。

この特定の場合、コンストラクター参照は適切ではありません。代わりにラムダを使用してください。 Supplier<? extends V>computeIfAbsentで使用され、その後ArrayList::newが適切です。

77
Tagir Valeev