Java >> Java チュートリアル >  >> Java

マイナー GC vs メジャー GC vs フル GC

Plumbr の GC 一時停止検出機能を使用している間、私はこのテーマに関する多数の記事、本、およびプレゼンテーションに取り組むことを余儀なくされました。旅の間中、マイナー、メジャー、フル GC イベントの (誤) 使用について、私は繰り返し混乱しました。これがこのブログ投稿につながり、この混乱の一部を解消できれば幸いです.

この投稿では、読者が JVM に組み込まれている一般的なガベージ コレクションの原則に精通していることを期待しています。ヒープの Eden、Survivor、Tenured/Old スペースへの分割、世代仮説、およびさまざまな GC アルゴリズムは、この投稿の範囲を超えています。


マイナー GC

Young スペース (Eden スペースと Survivor スペースで構成される) からガベージを収集することを マイナー GC と呼びます .この定義は明確であり、一様に理解されています。ただし、マイナー ガベージ コレクション イベントを処理する際に知っておくべき興味深い点がいくつかあります。

<オール>
  • マイナー GC は、JVM が新しいオブジェクトにスペースを割り当てることができない場合に常にトリガーされます。エデンがいっぱいになります。したがって、割り当て率が高いほど、マイナー GC が実行される頻度が高くなります。
  • プールがいっぱいになるたびに、その内容全体がコピーされ、ポインタは空きメモリをゼロから追跡し始めることができます。そのため、従来の Mark、Sweep、および Compact の代わりに、Eden および Survivor スペースのクリーニングは代わりに Mark and Copy を使用して実行されます。したがって、断片化は実際には Eden または Survivor スペース内では発生しません。書き込みポインターは、常に使用済みプールの一番上にあります。
  • マイナー GC イベント中、Tenured 世代は事実上無視されます。在職世代から若い世代への言及は事実上と見なされます GC ルート。マークアップ フェーズでは、若い世代から Tenured 世代への参照は単純に無視されます。
  • 常識に反して、すべてのマイナー GC ストップ・ザ・ワールドの一時停止をトリガーする 、アプリケーション スレッドを停止します。ほとんどのアプリケーションでは、一時停止の長さはレイテンシーに関しては無視できます。これは、Eden 内のほとんどのオブジェクトがガベージと見なされ、Survivor/Old スペースにコピーされない場合に当てはまります。その逆で、ほとんどの新生オブジェクトが GC に適していない場合、マイナー GC の一時停止にかなりの時間がかかり始めます。
  • したがって、マイナー GC の場合、状況はかなり明確でした。すべてのマイナー GC は、若い世代をクリーンアップします .

    メジャー GC vs フル GC

    これらの用語には正式な定義がないことに注意してください。 JVM仕様にもガベージコレクションの研究論文にもありません。しかし一見しただけでは、マイナー GC クリーニングのヤング スペースについて正しいとわかっていることに基づいてこれらの定義を構築するのは簡単なはずです。

    • メジャー GC Tenured スペースを清掃しています。
    • フル GC Young スペースと Tenured スペースの両方のヒープ全体をクリーニングしています。

    残念ながら、これはもう少し複雑でわかりにくいものです。まず、多くのメジャー GC はマイナー GC によってトリガーされるため、多くの場合、2 つを分離することは不可能です。一方、最新のガベージ コレクションの多くは、Tenured スペースの部分的なクリーニングを実行するため、「クリーニング」という用語の使用は部分的にしか正しくありません。

    これにより、GC がメジャー GC と呼ばれるかフル GC と呼ばれるかを心配する代わりに、目前の GC がすべてのアプリケーション スレッドを停止したかどうか、またはアプリケーション スレッドと同時に進行できたかどうかを調べることに集中する必要があります。 .

    この混乱は、JVM 標準ツールにも組み込まれています。私が言いたいことは、例を通して最もよく説明されています。 Concurrent Mark and Sweep コレクター (-XX:+UseConcMarkSweepGC) で実行されている JVM で GC をトレースする 2 つの異なるツールの出力を比較してみましょう。 )

    最初の試みは、jstat を介して洞察を得ることです。 出力:

    my-precious: me$ jstat -gc -t 4235 1s
    Time S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
     5.7 34048.0 34048.0  0.0   34048.0 272640.0 194699.7 1756416.0   181419.9  18304.0 17865.1 2688.0 2497.6      3    0.275   0      0.000    0.275
     6.7 34048.0 34048.0 34048.0  0.0   272640.0 247555.4 1756416.0   263447.9  18816.0 18123.3 2688.0 2523.1      4    0.359   0      0.000    0.359
     7.7 34048.0 34048.0  0.0   34048.0 272640.0 257729.3 1756416.0   345109.8  19072.0 18396.6 2688.0 2550.3      5    0.451   0      0.000    0.451
     8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0  444982.5  19456.0 18681.3 2816.0 2575.8      7    0.550   0      0.000    0.550
     9.7 34048.0 34048.0 34046.7  0.0   272640.0 16777.0  1756416.0   587906.3  20096.0 19235.1 2944.0 2631.8      8    0.720   0      0.000    0.720
    10.7 34048.0 34048.0  0.0   34046.2 272640.0 80171.6  1756416.0   664913.4  20352.0 19495.9 2944.0 2657.4      9    0.810   0      0.000    0.810
    11.7 34048.0 34048.0 34048.0  0.0   272640.0 129480.8 1756416.0   745100.2  20608.0 19704.5 2944.0 2678.4     10    0.896   0      0.000    0.896
    12.7 34048.0 34048.0  0.0   34046.6 272640.0 164070.7 1756416.0   822073.7  20992.0 19937.1 3072.0 2702.8     11    0.978   0      0.000    0.978
    13.7 34048.0 34048.0 34048.0  0.0   272640.0 211949.9 1756416.0   897364.4  21248.0 20179.6 3072.0 2728.1     12    1.087   1      0.004    1.091
    14.7 34048.0 34048.0  0.0   34047.1 272640.0 245801.5 1756416.0   597362.6  21504.0 20390.6 3072.0 2750.3     13    1.183   2      0.050    1.233
    15.7 34048.0 34048.0  0.0   34048.0 272640.0 21474.1  1756416.0   757347.0  22012.0 20792.0 3200.0 2791.0     15    1.336   2      0.050    1.386
    16.7 34048.0 34048.0 34047.0  0.0   272640.0 48378.0  1756416.0   838594.4  22268.0 21003.5 3200.0 2813.2     16    1.433   2      0.050    1.484

    このスニペットは、JVM が起動された後の最初の 17 秒から抽出されます。この情報に基づいて、マイナー GC を 12 回実行した後、50ms にわたって 2 回のフル GC を実行したと結論付けることができました。 合計で。 jconsole や jvisualvm などの GUI ベースのツールでも同じ確認が得られます。

    この結論に同意する前に、同じ JVM の起動から収集されたガベージ コレクション ログの出力を見てみましょう。どうやら -XX:+PrintGCDetails 別のより詳細なストーリーを教えてくれます:

    java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer
    3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs] 
    4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs] 
    ... cut for brevity ...
    11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs] 
    12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs] 
    12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs] 
    13.098: [GC (CMS Initial Mark) [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), 0.0041705 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] 
    13.102: [CMS-concurrent-mark-start]
    13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs] 
    13.341: [CMS-concurrent-preclean-start]
    13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
    13.350: [CMS-concurrent-abortable-preclean-start]
    13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs] 
    14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs] 
    14.366: [GC (CMS Final Remark) [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), 0.0462010 secs] [Times: user=0.14 sys=0.00, real=0.05 secs] 
    14.412: [CMS-concurrent-sweep-start]
    14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs] 
    14.633: [CMS-concurrent-reset-start]
    14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

    この情報に基づいて、実際に 12 回のマイナー GC 実行後に「何か違う」ことが起こり始めたことがわかります。しかし、2 回のフル GC の実行ではなく、この「別のもの」は、実際には異なるフェーズで構成される Tenured 世代で実行されている 1 つの GC でした。

    • 0.0041705 秒または約 4 ミリ秒にわたる初期マーク フェーズ。このフェーズは、最初のマーキングのためにすべてのアプリケーション スレッドを停止する世界停止イベントです。
    • マークアップ フェーズとプレクリーン フェーズを同時に実行しました。これらはアプリケーション スレッドと同時に実行されます
    • 最終リマーク フェーズ。0.0462010 秒または約 46 ミリ秒にわたります。このフェーズは再びストップ・ザ・ワールド・イベントです。
    • 同時に実行されるスイープ操作。名前が示すように、このフェーズもアプリケーション スレッドを停止することなく同時に実行されます。

    したがって、実際のガベージ コレクション ログからわかることは、2 つのフル GC 操作ではなく、古い領域をクリーニングするメジャー GC が 1 つだけ実際に実行されたことです。

    レイテンシーの後である場合は、jstat によって明らかにされたデータに基づいて決定を下します あなたを正しい決断へと導いたでしょう。その時点ですべてのアクティブなスレッドのレイテンシに影響を与える、合計 50 ミリ秒の 2 つの stop-the-world イベントが正しくリストされました。しかし、スループットを最適化しようとしている場合は、誤った方向に進んでいたでしょう。ストップ・ザ・ワールドの最初のマークと最後のリマーク・フェーズだけをリストすると、jstat の出力は、実行中の並行作業を完全に隠してしまいます。

    結論

    状況を考えると、マイナー、メジャー、またはフル GC の観点から考えるのは避けたほうがよいでしょう。代わりに、アプリケーションのレイテンシまたはスループットを監視し、GC イベントを結果にリンクします。これらのイベントに加えて、特定の GC イベントがすべてのアプリケーション スレッドを強制的に停止させたのか、それとも同時に処理されたイベントの一部だったのかについての情報が必要です。

    コンテンツを楽しんでいただけたなら、これはガベージ コレクション ハンドブックの例の章です。ハンドブック全体は、2015 年 3 月中にリリースされようとしています。

    Java タグ