実稼働環境でかなり不快な経験をしたため、_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
の場合にも発生します。)
最初の場合(ArrayList::new
)最初の容量引数を使用する constructor を使用していますが、2番目の場合は使用していません。大きな初期容量(コード内のindex
)により、大きなObject[]
が割り当てられ、OutOfMemoryError
sになります。
以下は、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
でも同様のことが起こります。
computeIfAbsent
署名は次のとおりです。
V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
したがって、mappingFunction
はreceives引数を1つ持つ関数です。あなたの場合K = Integer
およびV = 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
につながります。
この特定の場合、コンストラクター参照は適切ではありません。代わりにラムダを使用してください。 Supplier<? extends V>
はcomputeIfAbsent
で使用され、その後ArrayList::new
が適切です。