web-dev-qa-db-ja.com

なぜJavaはガベージコレクターを実行するのに長い間待つのですか?

Play!Framework を使用してJava Webアプリを構築しています。 playapps.net でホストしています。提供されているメモリ消費量のグラフにしばらく戸惑います。サンプルは次のとおりです。

Heap Memory

グラフは、一貫しているが名目上の活動の期間からのものです。メモリのフォールオフをトリガーするために何もしなかったので、ガベージコレクタが許容メモリ消費量にほぼ達したときに実行されたため、これが発生したと考えられます。

私の質問:

  • アプリケーションの実行時にすべてのメモリがガベージコレクタによって正しく再利用されるように見えるため、アプリケーションにメモリリークがないと想定するのは公平ですか?
  • (タイトルから)Javaがガベージコレクターを実行するために可能な最後の秒まで待機しているのはなぜですか?メモリ消費量がグラフの上位4分の1に増加するにつれて、パフォーマンスが大幅に低下します。
  • 上記の私の主張が正しい場合、どうすればこの問題を修正できますか?私が読んだ他の投稿SOは、ニュートラルからSystem.gc()の呼び出しに反対しているようです( "これはGCを実行するための要求にすぎないため、JVMはあなたを無視する可能性があります")完全に反対(" System.gc()に依存するコードは根本的に壊れています))または私はここでベースから外れており、この動作と断続的なパフォーマンスを引き起こしている自分のコードの欠陥を探す必要があります損失?

[〜#〜]更新[〜#〜]
PlayApps.netでこの質問を指摘し、ここでいくつかのポイントに言及するディスカッションを開始しました。具体的には、完全なGCの設定に関する@Affeのコメントは非常に保守的に設定されており、@ G_Hの初期および最大ヒープサイズの設定に関するコメントです。

これが ディスカッションへのリンク ですが、残念ながらそれを表示するにはplayappsアカウントが必要です。

フィードバックが届いたら、ここで報告します。皆さんの回答に感謝します。私はすでに彼らからたくさんのことを学びました!

解決策
Playappsのサポートはまだ素晴らしいですが、私には多くの提案がありませんでした。キャッシュを広範囲に使用していると、オブジェクトが必要以上に長く存続する可能性があると考えられていましたが、そうではありません。ケース。私はまだたくさんのことを学びました(woo hoo!)、そして私は@Ryan Amosに半日ごとにSystem.gc()を呼び出すという彼の提案を受けたので、緑色のチェックをしました。これは今のところうまく機能しています。

50
goggin13

Javaは、必要になるまでガベージクリーナーを実行しません。これは、ガベージクリーナーは処理速度を大幅に低下させるため、それほど頻繁に実行するべきではないためです。 3時間ごとなど、もっと頻繁に掃除をするようにスケジュールしても大丈夫だと思います。アプリケーションが完全なメモリを消費しない場合は、ガベージクリーナーを実行する理由はありません。そのため、Javaは、メモリが非常に多い場合にのみ実行します。

したがって、基本的に、他の人の言うことを心配する必要はありません。最も効果的なことを実行してください。 66%のメモリでガベージクリーナーを実行するとパフォーマンスが向上する場合は、それを実行してください。

19
Ryan Amos

詳細な答えは、使用しているガベージコレクターによって異なりますが、すべての(最新のSun/Oracle)GCで基本的に同じものがいくつかあります。

グラフの使用量が減少するのを見るたびに、それはガベージコレクションです。ヒープが解放される唯一の方法は、ガベージコレクションを使用することです。ガベージコレクションには、マイナーとフルの2種類があります。ヒープは、2つの基本的な「領域」に分割されます。若くて終身在職中。 (実際にはもっと多くのサブグループがあります。)Youngでスペースを占有していて、マイナーGCがメモリを解放するときにまだ使用されているものはすべて、「昇格」してテニュアになります。何かが飛躍を終わらせると、ヒープに空き領域がなくなり、完全なガベージコレクションが必要になるまで、それは無期限に存在します。

したがって、そのグラフの解釈の1つは、若い世代はかなり小さく(デフォルトでは、一部のJVMではヒープ全体のかなり小さい%になる可能性があります)、オブジェクトを比較的非常に長い間「存続」させているということです。 (おそらく、Webセッションでそれらへの参照を保持していますか?)したがって、オブジェクトは、JVMが正常にメモリ不足になるまで無期限に存続する、テニュアスペースに昇格するまでガベージコレクションを「存続」させます。

繰り返しますが、それはあなたが持っているデータに適合する一般的な状況の1つにすぎません。何が起こっているのかを実際に確認するには、JVM構成とGCログに関する完全な詳細が必要になります。

22
Affe

グラフはドロップするまで厳密に上向きに傾斜していませんが、局所的な変動は小さいことに気づいています。確かではありませんが、ガベージコレクションが行われていなければ、メモリ使用量がこれらの小さな低下を示すとは思いません。

Javaにはマイナーコレクションとメジャーコレクションがあります。マイナーコレクションは頻繁に発生しますが、メジャーコレクションはまれであり、パフォーマンスが低下します。マイナーコレクションは、メソッド内で作成された短命のオブジェクトインスタンスのようなものを一掃する傾向があります。主要なコレクションはさらに多くを削除します。これはおそらくグラフの最後で起こったことです。

さて、これを入力している間に投稿されたいくつかの回答は、ガベージコレクター、オブジェクト生成などの違いに関する良い説明を提供します。しかし、それでも、深刻なクリーニングが行われるまでに非常に長い時間(24時間近く)かかる理由は説明されていません。

起動時にJVMに設定できる2つの重要な点は、最大許容ヒープサイズと初期ヒープサイズです。最大値はハード制限です。これに達すると、ガベージコレクションを増やしてもメモリ使用量は減りません。オブジェクトやその他のデータに新しいスペースを割り当てる必要がある場合は、OutOfMemoryErrorが発生します。ただし、内部的にはソフト制限もあります。それは現在のヒープサイズです。 JVMは、最大量のメモリをすぐに飲み込むことはありません。代わりに、最初のヒープサイズから開始し、必要に応じてヒープを増やします。動的に増加する可能性のあるJVMのRAM)と少し考えてください。

アプリケーションの実際のメモリ使用量が現在のヒープサイズに達し始めた場合、通常、ガベージコレクションが開始されます。これによりメモリ使用量が減少する可能性があるため、ヒープサイズを増やす必要はありません。ただし、アプリケーションが現在そのすべてのメモリを必要とし、ヒープサイズを超える可能性もあります。その場合、設定された上限にまだ達していない場合は増加します。

ここで、初期ヒープサイズが最大値と同じ値に設定されている場合があります。そうだとすると、JVMはすぐにそのすべてのメモリを占有します。アプリケーションがメモリ使用量のヒープサイズに達するのに十分なガベージを蓄積するまでには、非常に長い時間がかかります。しかし、その瞬間、あなたは大きなコレクションを見るでしょう。十分に小さいヒープから始めて、ヒープを大きくすることで、メモリの使用を必要なものに制限します。

これは、グラフがヒープの使用を示しており、割り当てられたヒープサイズを示していないことを前提としています。そうではなく、実際にヒープ自体がこのように成長しているのを確認している場合は、別のことが起こっています。ガベージコレクションの内部とそのスケジューリングについて、ここで何が起こっているのかを完全に確実にするのに十分な知識がないことを認めます。これのほとんどは、プロファイラーでのアプリケーションのリークの観察によるものです。したがって、誤った情報を提供した場合は、この回答を削除します。

12
G_H

私はそのようなグラフを作成し、あなたが説明するように行動するアプリを持っていました。私はCMSコレクター(-XX:+ UseConcMarkSweepGC)を使用していました。これが私の場合に起こっていたことです。

アプリケーション用に十分なメモリを構成していなかったため、時間の経過とともにヒープ内で断片化の問題が発生していました。これにより、GCの頻度がますます高くなりましたが、実際にはOOMEをスローしたり、CMSからシリアルコレクターにフェイルアウトしたりすることはありませんでした(その場合はそうなるはずです)。世界)、アプリケーションの同時時間(GCはアプリケーションスレッドで実行)は、これらの計算では無視されます。私はいくつかのパラメーターを調整し、主に(非常に大きな新しいスペースで)全体のがらくたロードにより多くのヒープを与え、-XX:CMSFullGCsBeforeCompaction = 1を設定すると、問題は発生しなくなりました。

2
Kevin Lafayette

お気づきかもしれませんが、これは影響しません。ガベージコレクションは、JVMが実行する必要があると感じた場合にのみ開始されます。これは最適化のために発生します。単一の完全なコレクションを作成し、完全なクリーンアップを実行できる場合、多くの小さなコレクションを実行する必要はありません。

現在のJVMには、いくつかの非常に興味深いアルゴリズムが含まれており、ガベージコレクション自体は3つの異なる領域に分割されています。これについては、さらに詳しく知ることができます ここ 、サンプルは次のとおりです。

3種類の収集アルゴリズム

HotSpot JVMは3つのGCアルゴリズムを提供し、それぞれが特定の世代内の特定のタイプのコレクション用に調整されています。コピー(スカベンジとも呼ばれます)コレクションは、新世代のヒープ内の短命のオブジェクトをすばやくクリーンアップします。マークコンパクトアルゴリズムは、より低速でより堅牢な手法を使用して、旧世代のヒープでより長寿命のオブジェクトを収集します。インクリメンタルアルゴリズムは、一時停止を最小限に抑えながら堅牢なGCを実行することにより、旧世代のコレクションを改善しようとします。

コレクションのコピー/スカベンジ

コピーアルゴリズムを使用すると、JVMは、ごみを収集して削除するためのJava用語である小さなスカベンジを作成するだけで、新世代のオブジェクトスペース(edenとも呼ばれます)内のほとんどのオブジェクトを再利用します。寿命の長いオブジェクトは、最終的に古いオブジェクトスペースにコピーまたは保持されます。

マーク-コンパクトコレクション

より多くのオブジェクトが保有されるようになると、古いオブジェクトスペースは最大占有率に達し始めます。古いオブジェクトスペースでオブジェクトを収集するために使用されるマークコンパクトアルゴリズムには、新しいオブジェクトスペースで使用されるコピー収集アルゴリズムとは異なる要件があります。

マークコンパクトアルゴリズムは、最初にすべてのオブジェクトをスキャンし、到達可能なすべてのオブジェクトをマークします。次に、死んだオブジェクトの残りのすべてのギャップを圧縮します。マークコンパクトアルゴリズムは、コピー収集アルゴリズムよりも多くの時間を占有します。ただし、必要なメモリが少なくて済み、メモリの断片化がなくなります。

インクリメンタル(電車)コレクション

新世代のコピー/スカベンジと旧世代のマークコンパクトアルゴリズムでは、すべてのJVMの一時停止を排除することはできません。このような一時停止は、ライブオブジェクトの数に比例します。一時停止のないGCの必要性に対処するために、HotSpot JVMは、増分収集またはトレーニング収集も提供します。

インクリメンタルコレクションは、古いオブジェクトコレクションの一時停止を、大きなオブジェクト領域がある場合でも多くの小さな一時停止に分割します。このアルゴリズムには、新旧の世代だけでなく、多くの小さなスペースで構成される中間世代があります。インクリメンタルコレクションに関連するオーバーヘッドがあります。速度が10%低下する場合があります。

-Xincgcおよび-Xnoincgcパラメーターは、増分収集の使用方法を制御します。 HotSpot JVMの次のリリースであるバージョン1.4は、おそらくインクリメンタルアルゴリズムのバリエーションとなる、継続的で一時停止のないGCを試行します。インクリメンタルコレクションは間もなく変更されるため、ここでは説明しません。

この世代別ガベージコレクターは、今日の問題に対して私たちが持っている最も効率的な解決策の1つです。

2

おそらく、24時間ごとにクリアされるメモリリークがあります。

0
irreputable