ちょっとした情報を集めて(公式ドキュメントがかなり悪いので、これは驚くほど難しいです)、私は判断しました...
一般に、これが発生する理由は 2 つあります。どちらも空き領域の断片化 (つまり、大きなオブジェクトを割り当てることができないように小さな断片に存在する空き領域) に関連しています。まず、ガベージ コレクターは圧縮を行わない可能性があります。つまり、メモリの最適化を行いません。圧縮を行うコレクターでさえ、それを完全にうまく行うことはできません。第 2 に、ガベージ コレクターは通常、メモリ領域をさまざまな種類のオブジェクト用に予約する領域に分割し、領域から空きメモリを取得して、それを必要とする領域に与えることを考えない場合があります。
CMS ガベージ コレクタは圧縮を行いませんが、他のもの (シリアル、パラレル、パラレル、および G1) は圧縮を行います。Java 8 のデフォルトのコレクターは ParallelOld です。
すべてのガベージ コレクターはメモリを領域に分割します。私の知る限り、それらはすべて怠惰すぎて、OOM エラーを防ぐために一生懸命努力することができません。コマンド ライン オプション-XX:+PrintGCDetails
は、一部のコレクターにとって、領域のサイズと空き容量を表示するのに非常に役立ちます。
さまざまなガベージ コレクターとチューニング オプションを試すことができます。私の質問に関しては、G1
コレクター (JVM フラグで有効化-XX:+UseG1GC
) が私が抱えていた問題を解決しました。ただし、これは基本的に偶然によるものです (他の状況では、OOM がより迅速に発生します)。一部のコレクター (シリアル、cms、および G1) には、さまざまな領域のサイズを選択するための広範な調整オプションがあり、問題を解決しようとして無駄に時間を無駄にすることができます。
最終的に、本当の解決策はかなり不快です。まず、より多くの RAM をインストールすることです。2 つ目は、より小さな配列を使用することです。第三に、使用することByteBuffer.allocateDirect
です。ダイレクト バイト バッファー (およびその int/float/double ラッパー) は、OS のネイティブ ヒープに割り当てられる、配列に似たパフォーマンスを持つ配列に似たオブジェクトです。OS ヒープは CPU の仮想メモリ ハードウェアを使用し、断片化の問題がなく、ディスクのスワップ スペースを効果的に使用することもできます (使用可能な RAM よりも多くのメモリを割り当てることができます)。ただし、大きな欠点は、JVM がダイレクト バッファの割り当てをいつ解除すべきかを本当に認識していないことです。このオプションは、存続期間の長いオブジェクトにはより望ましいものです。最後の、おそらく最良の、そして確かに最も不快なオプションは、割り当てと割り当て解除ですJNI 呼び出しを使用してネイティブにメモリを使用し、Java でラップして使用しますByteBuffer
。