System.gc()のガイド

1. 概要

このチュートリアルでは、_java.lang_パッケージにある_System.gc()_メソッドを調査します。
_System.gc()_を明示的に呼び出すことは、悪い習慣であることが知られています。 このメソッドを呼び出すのに役立つ理由と、ユースケースがあるかどうかを理解してみましょう。

[[garbage collection]]
=== 2. ガベージコレクション

Java Virtual Machineは、指示がある場合にガベージコレクションを実行することを決定します。 これらの表示は、https://www.baeldung.com/jvm-garbage-collectors [GC implementation]ごとに異なります。 それらは異なるヒューリスティックに基づいています。 ただし、GCが確実に実行される瞬間がいくつかあります。
  • 新世代(Tenuredスペース)がいっぱいで、マイナーGCをトリガーします

  • 古い世代(Eden Survivor0 Survivor1スペース)はいっぱいです。
    メジャー/フルGCをトリガーします

  • GCの実装に依存しない唯一のものは、オブジェクトのガベージコレクションの適格性です*。

    ここで、_System.gc()_メソッド自体を見てみましょう。

3. System.gc()

メソッドの呼び出しは簡単です。
System.gc()
公式のhttps://docs.oracle.com/javase/8/docs/api/java/lang/System.html#gc--[Oracle documentation]には次のように記載されています。
___________________________________________________________________________________________________________________________________________________________________________________________ _gc_メソッドを呼び出す_suggests_は、Java仮想マシンが現在使用しているメモリをすぐに再利用できるようにするために、未使用オブジェクトのリサイクルに努力します。 ___________________________________________________________________________________________________________________________________________________________________________________________
*実際のGCがトリガーされる保証はありません*。
_System.gc()_ https://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html#other_considerations [メジャーGCをトリガー]。 したがって、ガベージコレクターの実装によっては、世界の停止フェーズに時間がかかるリスクがあります。 その結果、*パフォーマンスが大幅に低下する可能性のある信頼性の低いツールがあります*。
明示的なガベージコレクションの呼び出しが存在することは、すべての人にとって重大な危険です。
_-XX:DisableExplicitGC_ JVMフラグを使用することにより、_System.gc()_が作業を実行しないようにすることができます。

3.1. 性能調整

_OutOfMemoryError、_をスローする直前に、JVMがフルGCを実行することに注意してください。 したがって、* __ System.gc()__を明示的に呼び出しても、失敗から救うことはできません*。
*最近のガベージコレクターは非常に賢明です。*適切な判断を下せるように、メモリ使用量やその他の統計に関するすべての知識を持っています。 したがって、それらを信頼する必要があります。
メモリの問題の場合、https://docs.oracle.com/javase/9​​/gctuning/JSGCT.pdf [設定の束]を使用して、アプリケーションを調整するように変更できます。別のガベージコレクタの選択から、必要なアプリケーション時間/ GC時間比を設定し、最後に、メモリセグメントの固定サイズの設定で終了します。
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/cms-6.html [明示的な呼び出しによるFull GCの影響を緩和する]の方法もあります。 フラグのいずれかを使用できます。
-XX:+ExplicitGCInvokesConcurrent
or:
-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
*アプリを適切に動作させたい場合は、実際のメモリ問題を解決する必要があります。*
次の章では、_System.gc()_を明示的に呼び出すことが有用であると思われる場合の実用的な例を見ていきます。

4. 使用例

4.1. シナリオ

テストアプリを作成しましょう。 * _System.gc()_を呼び出すと便利な状況を見つけたい*。
マイナーガベージコレクションは、メジャーガベージコレクションより頻繁に発生します。 したがって、おそらく後者に注目する必要があります。 単一のオブジェクトがいくつかのコレクションを「生き残った」場合、GCルートから到達可能な場合は、1つのオブジェクトが古いスペースに移動されます。
しばらく生きているオブジェクトの膨大なコレクションがあると想像してみましょう。 次に、ある時点で、オブジェクトのコレクションをクリアします。 _System.gc()_を実行するのに良い瞬間かもしれません。

4.2. デモアプリケーション

このシナリオをシミュレートできるシンプルなコンソールアプリを作成します。
public class DemoApplication {

    private static final Map<String, String> cache = new HashMap<String, String>();

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNext()) {
            final String next = scanner.next();
            if ("fill".equals(next)) {
                for (int i = 0; i < 1000000; i++) {
                    cache.put(randomUUID().toString(), randomUUID().toString());
                }
            } else if ("invalidate".equals(next)) {
                cache.clear();
            } else if ("gc".equals(next)) {
                System.gc();
            } else if ("exit".equals(next)) {
                System.exit(0);
            } else {
                System.out.println("unknown");
            }
        }
    }
}

4.3. デモを実行する

いくつかの追加フラグを使用してアプリケーションを実行しましょう。
-XX:+PrintGCDetails -Xloggc:gclog.log -Xms100M -Xmx500M -XX:+UseConcMarkSweepGC
GC情報を記録するには、最初の2つのフラグが必要です。 次の2つのフラグは、初期ヒープサイズを設定してから、最大ヒープサイズを設定しています。 GCを強制的にアクティブにするために、ヒープサイズを小さく保つ必要があります。 最後に、CMS – Concurrent Mark and Sweepガベージコレクターを使用することにしました。 アプリを実行します!
最初に、*テニュアスペースを埋めよう*。 タイプ_fill._
link:/java-verbose-gc[_gclog.log_を調査]ファイルで何が起こったかを確認できます。 約15のコレクションが表示されます。 単一のコレクションについて記録される行は次のようになります。
197.057: [GC (Allocation Failure) 197.057: [ParNew: 67498K->40K(75840K), 0.0016945 secs]
  168754K->101295K(244192K), 0.0017865 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] secs]
ご覧のとおり、メモリがいっぱいです。
次に、_gc_と入力して_System.gc()_ *を強制します。 メモリ使用量が大幅に変化しなかったことがわかります。
238.810: [Full GC (System.gc()) 238.810: [CMS: 101255K->101231K(168352K); 0.2634318 secs]
  120693K->101231K(244192K), [Metaspace: 32186K->32186K(1079296K)], 0.2635908 secs]
  [Times: user=0.27 sys=0.00, real=0.26 secs]
さらに数回実行すると、メモリサイズが同じレベルのままであることがわかります。
_invalidate_と入力して、キャッシュを*クリア*しましょう。 _gclog.log_ファイルにログ行が表示されなくなるはずです。
さらに数回キャッシュを埋めることができますが、GCは発生していません。 これは、ガベージコレクターの裏をかくことができる瞬間です。 GCを強制すると、次のような行が表示されます。
262.124: [Full GC (System.gc()) 262.124: [CMS: 101523K->14122K(169324K); 0.0975656 secs]
  103369K->14122K(245612K), [Metaspace: 32203K->32203K(1079296K)], 0.0977279 secs]
  [Times: user=0.10 sys=0.00, real=0.10 secs]
私たちは、メモリの印象的な量をリリースしました! *しかし、それは今本当に本当に必要でしたか?*何が起こりましたか?
この例によると、ビッグオブジェクトをリリースしたりキャッシュを無効にしたりするときに__System.gc()__mightを呼び出すのは魅力的です。

5. その他の使用法

_System.gc()_メソッドの明示的な呼び出しが有用な場合は、ほとんどありません。
考えられる理由の1つは、サーバーの起動後にメモリをクリーニングすることです—多くの準備を行うサーバーまたはアプリケーションを起動しています。 その後、ファイナライズするオブジェクトがたくさんあります。 ただし、そのような準備の後の清掃は私たちの責任ではありません。
もう1つは、*メモリリーク解析*__— __itは、実稼働コードに保持したいものよりもデバッグの実践です。 _System.gc()_を呼び出してヒープスペースがまだ高いことを確認することは、https://www.baeldung.com/java-memory-leaks [メモリリーク]を示している可能性があります。

6. 概要

この記事では、__ System.gc()__methodと、それがいつ役立つと思われるかを調査しました。
*アプリの正確性については、これに頼るべきではありません。*ほとんどの場合、GCは私たちよりも賢く、メモリに問題がある場合は、そのような明示的な呼び出しを行う代わりに仮想マシンのチューニングを検討する必要があります。
いつものように、この記事で使用されるコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jvm[GitHubで]にあります。