1. 概要

このチュートリアルでは、 java .langパッケージにあるSystem.gc()メソッドを調査します。

System.gc()を明示的に呼び出すことは、悪い習慣であることが知られています。 このメソッドを呼び出すときに、その理由とユースケースがあるかどうかを理解してみましょう。

2. ガベージコレクション

Java仮想マシンは、ガベージコレクションを実行する必要がある場合に実行することを決定します。 これらの表示は、GC実装ごとに異なります。 それらは異なるヒューリスティックに基づいています。 ただし、GCが確実に実行される瞬間がいくつかあります。

  • 古い世代(Tenured space)がいっぱいで、メジャー/フルGCがトリガーされます
  • 新世代(Eden + Survivor0 + Survivor1スペース)がいっぱいで、マイナーGCがトリガーされます

GCの実装から独立しているのは、ガベージコレクションされるオブジェクトの適格性だけです。

次に、 System.gc()メソッド自体を見ていきます。

3. System.gc()

メソッドの呼び出しは簡単です。

System.gc()

公式のOracleドキュメントには次のように記載されています。

gc メソッドを呼び出すと、Java仮想マシンは、現在占有しているメモリをすばやく再利用できるようにするために、未使用のオブジェクトのリサイクルに労力を費やすことを提案します。

実際のGCがトリガーされる保証はありません

System.gc()はメジャーGCをトリガーします。 したがって、ガベージコレクターの実装によっては、ストップザワールドフェーズに時間を費やすリスクがあります。 その結果、信頼性の低いツールがあり、パフォーマンスが大幅に低下する可能性があります

明示的なガベージコレクションの呼び出しの存在は、すべての人にとって深刻な危険信号となるはずです。

-XX:DisableExplicitGC JVMフラグを使用することで、 System.gc()が作業を行わないようにすることができます。

3.1. 性能調整

OutOfMemoryError、をスローする直前に、JVMは完全なGCを実行することに注意してください。 したがって、 System.gc()を明示的に呼び出しても、失敗から私たちを救うことはできません。

最近のガベージコレクターは本当に賢いです。彼らは適切な決定を下すことができるようにメモリ使用量と他の統計についてのすべての知識を持っています。 したがって、私たちはそれらを信頼する必要があります。

メモリの問題が発生した場合は、一連の設定を変更して、アプリケーションを調整できます。別のガベージコレクターの選択から始めて、目的のアプリケーション時間/ GC時間の比率を設定し、最後に設定を行います。メモリセグメントの固定サイズ。

明示的な呼び出しによって引き起こされるフルGCの影響を軽減する方法もあります。 フラグの1つを使用できます。

-XX:+ExplicitGCInvokesConcurrent

また:

-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

アプリを本当に正しく動作させたい場合は、根本的なメモリの問題を解決する必要があります。

次の章では、 System.gc()を明示的に呼び出すと便利なように見える実際の例を示します。

4. 使用例

4.1. シナリオ

テストアプリを書いてみましょう。 System.gc()を呼び出すと便利な状況を見つけたい

マイナーガベージコレクションは、メジャーガベージコレクションよりも頻繁に発生します。 したがって、おそらく後者に焦点を当てる必要があります。 単一のオブジェクトがいくつかのコレクションを「存続」し、GCルートからまだ到達可能である場合、単一のオブジェクトは保有スペースに移動されます。

しばらくの間生きているオブジェクトの膨大なコレクションがあると想像してみましょう。 次に、ある時点で、オブジェクトのコレクションをクリアしています。 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 MarkandSweepガベージコレクターを使用することを決定しました。 アプリを実行する時が来ました!

まず、保有スペースを埋めてみましょう。 fillと入力します。

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 と入力して、 force 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()を呼び出すと、大きなオブジェクトを解放したり、キャッシュを無効にしたりするときに魅力的に見える場合があります。

5. その他の使用法

System.gc()メソッドへの明示的な呼び出しが役立つ場合がある理由はほとんどありません。

考えられる理由の1つは、サーバーの起動後のメモリのクリーニングです。多くの準備を行うサーバーまたはアプリケーションを起動しています。 その後、ファイナライズするオブジェクトがたくさんあります。 ただし、そのような準備の後の清掃は私たちの責任ではありません。

もう1つは、メモリリーク分析 は、本番コードに保持したいものよりもデバッグの方法です。 System.gc()を呼び出して、ヒープスペースがまだ高いことを確認している場合は、メモリリークを示している可能性があります。

6. 概要

この記事では、 System.gc()メソッドと、それがいつ役立つと思われるかを調査しました。

私たちのアプリの正しさに関しては、決してそれに頼るべきではありません。 ほとんどの場合、GCは私たちよりも賢く、メモリに問題がある場合は、そのような明示的な呼び出しを行うのではなく、仮想マシンの調整を検討する必要があります。

いつものように、この記事で使用されているコードは、GitHubにあります。