学生から、アプリケーション内の Old Gen の使用率が CMSInitiatingOccupancyFraction パラメータで設定されたしきい値に達していないのに、なぜ CMS GC がトリガーされるのかとよく質問されます。彼らは、それは非常に奇妙であり、どこに問題があるのかわからないと言います。 [[267920]] 実際、CMS GC には、CMSInitiatingOccupancyFraction しきい値トリガーだけでなく、多くのトリガー条件があります。この記事では、ソース コードを通じて CMS GC をトリガーする条件を包括的に整理し、日常生活で遭遇する奇妙な CMS GC の問題を理解できるようにします。 皆さんの注意を引くために、まずいくつか質問をさせてください。 - Old Gen の使用率が 50% しかないのに、なぜ CMS GC が実行されるのでしょうか?
- Metaspace の使用によって CMS GC もトリガーされますか?
- Old Gen の使用率が非常に小さいのに CMS GC が実行される理由は何ですか?
トリガー条件 CMS GC は実装上、フォアグラウンド コレクターとバックグラウンド コレクターに分かれています。フォアグラウンド コレクターは比較的単純ですが、バックグラウンド コレクターはより複雑で、状況も多くなります。 次に、フォアグラウンド コレクターとバックグラウンド コレクターのトリガー条件をそれぞれ説明します。 注: この記事はJDK 8に基づいています 注: この記事では、CMS GC のトリガー条件についてのみ説明します。アルゴリズムの具体的なプロセスと、MSC (マーク スイープ コンパクション) をいつ実行するかは、この記事の範囲外です。 フォアグラウンドコレクター フォアグラウンドコレクターのトリガー条件は比較的単純です。通常、オブジェクトが割り当てられているが十分なスペースがない場合は、GC が直接トリガーされ、すぐにスペースが再利用されます。使用されるアルゴリズムは、圧縮なしのマークスイープです。 背景コレクター バックグラウンド コレクターのトリガー条件を説明する前に、まずバックグラウンド コレクターのプロセスについて説明します。 CMS バックグラウンド スレッドを継続的にスキャンします。主なプロセスは、バックグラウンド コレクターのトリガー条件が満たされているかどうかを判断することです。条件が満たされると、バックグラウンド収集が実行されます。 - void ConcurrentMarkSweepThread::run() {
- ...//省略
- 終了する必要がありますが、
- 次のサイクルの前にスリープします。
- if (_should_terminate) break;
- GCCause::Cause 原因 = _collector->_full_gc_requested?
- _collector->_full_gc_cause : GCCause::_cms_concurrent_mark;
- _collector->collect_in_background( false 、原因);
- }
- ...//省略
- }
各スキャン中、まず CMSWaitDuration 時間待機し、次に shouldConcurrentCollect 判定を実行して、CMS バックグラウンド コレクターのトリガー条件が満たされているかどうかを確認します。 CMSWaitDuration のデフォルト時間は 2 秒です (ビジネスでは CMS GC が頻繁に発生します。各 CMS GC 間の時間間隔に注意してください。2 秒の場合、基本的に CMS のバックグラウンド コレクターであると判断できます)。 - void ConcurrentMarkSweepThread::sleepBeforeNextCycle() {
- (!_終了する必要があります){
- (CMS増分モード)の場合{
- icms_wait();
- CMSWaitDuration >= 0 の場合
- //次の同期GC、つまり同時フルGCまで待機します
- // リクエストまたはタイムアウトのいずれか早い方。
- 清掃のためにcms_lock_on_scavengeを待機します(CMSWaitDuration);
- }
- 戻る;
- }それ以外{
- CMSWaitDuration >= 0 の場合
- //次の同期GC、つまり同時フルGCまで待機します
- // リクエストまたはタイムアウトのいずれか早い方。
- ロック解除のために CMS ロックを待機します。
- }それ以外{
- // cms_lockイベントが発生するか、 チェック間隔は shouldConcurrentCollectを永続的に呼び出す
- wait_on_cms_lock(CMSチェック間隔);
- }
- }
- // CMS 収集サイクルを開始する必要があるかどうかを確認します
- _collector->shouldConcurrentCollect() の場合 {
- 戻る;
- }
- // .. コレクション基準がまだ満たされていないので、戻りましょう
- //そしてもう少し待つ
- }
- }
では、shouldConcurrentCollect() メソッドの条件は何でしょうか? - ブールCMSCollector::shouldConcurrentCollect() {
- //*** トリガー状況
- _full_gc_requested の場合 {
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print_cr( "CMSCollector: 明示的な " のため収集します
- 「 gc リクエスト (または gc_locker)」 );
- }
- 戻る 真実;
- }
- // デバッグのために、コレクションのタイプを変更します。
- // 回転が ない 同時収集について
- // 型の場合、同時コレクションを開始しないでください。
- NOT_PRODUCT(
- if (RotateCMSCollectionTypes &&
- (_cmsGen->debug_collection_type() !=
- コンカレントマークスイープ生成::コンカレントコレクションタイプ)) {
- アサート(_cmsGen->debug_collection_type() !=
- ConcurrentMarkSweepGeneration::Unknown_collection_type、
- 「CMS コレクション タイプが不正です」 );
- 戻る 間違い;
- }
- )
- フリーリストロッカー x(これ);
- //
- // 開始に影響する多くの情報を出力します
- // コレクション。
- PrintCMSInitiationStatistics が stats().valid() の場合 {
- gclog_or_tty->print( "CMSCollector は同時収集する必要があります: " );
- gclog_or_tty->スタンプ();
- gclog_or_tty->print_cr( "" );
- 統計情報()。print_on(gclog_or_tty);
- gclog_or_tty->print_cr( "cms_gen_full までの時間 %3.7f" ,
- 統計()。cms_gen_full()までの時間。
- gclog_or_tty->print_cr( "free=" SIZE_FORMAT, _cmsGen-> free ());
- gclog_or_tty->print_cr( "連続利用可能=" SIZE_FORMAT,
- _cmsGen->連続利用可能());
- gclog_or_tty->print_cr( "promotion_rate=%g" , stats().promotion_rate());
- gclog_or_tty->print_cr( "cms_allocation_rate=%g" 、 stats().cms_allocation_rate());
- gclog_or_tty->print_cr( "占有率=%3.7f" 、 _cmsGen->占有率());
- gclog_or_tty->print_cr( "initiatingOccupancy=%3.7f" 、 _cmsGen->initiating_occupancy());
- gclog_or_tty->print_cr( "メタデータが%dに初期化されました" ,
- MetaspaceGC::should_concurrent_collect() を使用します。
- }
- //
- // 2番目のトリガー状況
- // 推定時間 CMS コレクションを完了する (cms_duration() )
- // CMS生成までの推定残り時間より短い
- //は いっぱいになったら、コレクションを開始します。
- if (!UseCMSInitiatingOccupancyOnly) {
- (統計()が有効()の場合){
- (統計().cms_startまでの時間() == 0.0)の場合){
- 戻る 真実;
- }
- }それ以外{
- //やや早めに保守的に収集したい 注文
- //試してみて CMS/プロモーション統計を「ブートストラップ」します。
- // このブランチは最初のCMSが成功した後は実行されません
- // コレクションを無効にしてください。統計が有効になるはずです。
- _cmsGen->occupancy() >= _bootstrap_occupancy の場合 {
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print_cr(
- 「CMSCollector: ブートストラップ統計を収集します:」
- " 占有率 = %f、ブート占有率 = %f" 、_cmsGen->occupancy()、
- _bootstrap_occupancy);
- }
- 戻る 真実;
- }
- }
- }
- // 3番目のトリガー状況
- // それ以外の場合は、収集サイクルを開始します。
- // 古い世代では、収集サイクルを開始する必要があります。それぞれが使用できる
- //この決定を行うための適切な基準。
- // XXX genの拡張が
- // 基準はこれとよく一致します。 XXXこれを修正する必要があります
- _cmsGen->should_concurrent_collect() の場合 {
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print_cr( "CMS 古い世代が開始されました" );
- }
- 戻る 真実;
- }
- // 4番目のトリガー状況
- // 増分コレクションが失敗する可能性があると判断した場合はコレクションを開始します。
- // これは 実際にはあまり生産的ではないだろう。
- // とにかく遅い。
- GenCollectedHeap* gch = GenCollectedHeap::heap();
- アサート(gch->collector_policy()->is_two_generation_policy()、
- 「以下の内容が正しいかどうか確認することをお勧めします」 );
- if (gch->incremental_collection_will_fail( true /* consult_young */)) {
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print( "CMSCollector: 増分コレクションが失敗するため、収集してください " );
- }
- 戻る 真実;
- }
- // 5番目のトリガー状況
- (MetaspaceGC::should_concurrent_collect())の場合{
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print( "CMSCollector: メタデータ割り当てのために収集します " );
- }
- 戻る 真実;
- }
- 戻る 間違い;
- }
上記のコードから、バックグラウンド コレクターには 5 つのトリガー状況があることがわかります。 1. 並列フルGCですか? つまり、GC の原因が gclocker で GCLockerInvokesConcurrent パラメータが設定されている場合、または GC の原因が javalangsystemgc (つまり、System.gc() 呼び出し) で ExplicitGCInvokesConcurrent パラメータが設定されている場合、バックグラウンド コレクターが 1 回トリガーされます。 2. 統計データに基づく動的計算 (UseCMSInitiatingOccupancyOnly が構成されていない場合のみ) UseCMSInitiatingOccupancyOnly が構成されていない場合、CMS GC が必要かどうかは統計データに基づいて動的に決定されます。 判断ロジックは、予測された CMS GC が完了するのに必要な時間が、古い世代がいっぱいになるまでにかかる推定時間よりも長い場合に、GC が実行されるというものです。これらの判断は、過去の CMS GC 統計指標に基づいて行う必要があります。ただし、最初の CMS GC では、統計データはまだ形成されておらず、無効です。このとき、Old Gen の使用率によって GC を実行するかどうかが決定されます。 - if (!UseCMSInitiatingOccupancyOnly) {
- (統計()が有効()の場合){
- (統計().cms_startまでの時間() == 0.0)の場合){
- 戻る 真実;
- }
- }それ以外{
- //やや早めに保守的に収集したい 注文
- //試してみて CMS/プロモーション統計を「ブートストラップ」します。
- // このブランチは最初のCMSが成功した後は実行されません
- // コレクションを無効にしてください。統計が有効になるはずです。
- _cmsGen->occupancy() >= _bootstrap_occupancy の場合 {
- if (Verbose && PrintGCDetails) {
- gclog_or_tty->print_cr(
- 「CMSCollector: ブートストラップ統計を収集します:」
- " 占有率 = %f、ブート占有率 = %f" 、_cmsGen->occupancy()、
- _bootstrap_occupancy);
- }
- 戻る 真実;
- }
- }
- }
リサイクルは何パーセントから始まりますか? (つまり、bootstrapoccupancy の値はいくらでしょうか?) 答えは 50% です。おそらくあなたも同じようなケースに遭遇したことがあるでしょう。 UseCMSInitiatingOccupancyOnly が構成されていない場合、古い世代が 50% を占めたときに CMS GC が実行されていることがわかりました。その時、あなたは混乱したかもしれません。 - _bootstrap_occupancy = (( double )CMSBootstrapOccupancy)/( double )100;
- //パラメータのデフォルト値
- 製品(uintx, CMSBootstrapOccupancy, 50,
- 「ブートストラップ収集統計の CMS 収集を開始する CMS 生成占有率」 )
3. 老将の状況から判断する - bool ConcurrentMarkSweepGeneration::should_concurrent_collect() 定数 {
- ロックをアサートする
- 占有率() > 開始占有率() の場合 {
- if (PrintGCDetails && Verbose) {
- gclog_or_tty->print( " %s: 占有率 %f / %f のため収集 " ,
- short_name()、occupancy()、initiating_occupancy());
- }
- 戻る 真実;
- }
- if (CMSInitiatingOccupancyOnly を使用する) {
- 戻る 間違い;
- }
- (expansion_cause() == CMSExpansionCause::_satisfy_allocation)の場合{
- if (PrintGCDetails && Verbose) {
- gclog_or_tty->print( " %s: 割り当てのために拡張されたため収集します " ,
- ショートネーム());
- }
- 戻る 真実;
- }
- _cmsSpace->should_concurrent_collect() の場合 {
- if (PrintGCDetails && Verbose) {
- gclog_or_tty->print( " %s: cmsSpace の指示に従って収集します " ,
- ショートネーム());
- }
- 戻る 真実;
- }
- 戻る 間違い;
- }
ソース コードからは、2 つの主なカテゴリがわかります。(a) Old Gen のスペース使用率がしきい値と比較されます。しきい値より大きい場合、CMS GC が実行されます (つまり、「occupancy() > initiatingoccupancy()」)。占有率は間違いなく Old Gen スペースの現在の使用率ですが、占有率の開始とは何でしょうか? - _cmsGen ->init_initiating_occupancy(CMSInitiatingOccupancyFraction、CMSTriggerRatio);
- ...
- void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) {
- assert(io <= 100 && tr <= 100, "引数を確認してください" );
- io >= 0 の場合
- _initiating_occupancy = ( double )io / 100.0;
- }それ以外{
- _initiating_occupancy = ((100 - MinHeapFreeRatio) +
- ( double )(tr *MinHeapFreeRatio) / 100.0)
- / 100.0;
- }
- }
CMSInitiatingOccupancyFraction パラメータの設定値が 0 より大きい場合は、「io / 100.0」であることがわかります。 CMSInitiatingOccupancyFraction パラメータ構成値が 0 未満の場合 (デフォルトは -1 であることに注意してください)、値は "((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0" になります。これは何ですか? 92%です。具体的な計算過程についてはここでは掲載しません。おそらく、CMSInitiatingOccupancyFraction が構成されていない場合は 92 であると、何らかの書籍やブログで学んだことがあるでしょう。しかし、実際には、CMSInitiatingOccupancyFraction が構成されていない場合は -1 であるため、しきい値は後者の 92% であり、CMSInitiatingOccupancyFraction の値が 92 であるわけではありません。 (b) 次に、UseCMSInitiatingOccupancyOnlyが設定されていない場合 ここには 2 つのサブカテゴリがあります。 - オブジェクトの割り当てにより Old Gen が拡張され、スペースが正常に割り当てられると、CMS GC が考慮されます。
- CMS Gen アイドル チェーンから判断すると、これは少し複雑で、まだ解明されていません。幸いなことに、デフォルトの構成によれば、ここでは実際には false が返されるため、このトリガー条件をデフォルトで考慮する必要はありません。
4. 増分GCが失敗するかどうかに基づく(悲観的な戦略) それはどういう意味ですか? 2世代GCシステムでは、主に若いGCが失敗するかどうかを指します。 Young GC が失敗したか失敗する可能性がある場合、JVM は CMS GC が必要であると判断する。 - ブール増分コレクションが失敗する(ブールconsult_young) {
- // 2世代システムを想定しています。最初の分離項は、
- // 増分コレクションは失敗しました。 ( 2 番目の論理和)
- // そうならないだろう。
- アサート(ヒープ()->コレクターポリシー()->2世代ポリシー()、
- 「次の定義はn(>2)世代システムには適さない可能性があります」 );
- incremental_collection_failed()を返します||
- (consult_young && !get_gen(0)->collection_attempt_is_safe());
- }
2つの判定条件「incrementalcollectionfailed()」と「!getgen(0)->collectionattemptissafe()」を見てみましょう。ここでのincrementalcollectionfailed()はYoung GCが失敗したことを意味します。失敗する理由としては、通常、Old Gen に昇格したオブジェクトを収容するのに十分なスペースがないことが挙げられます。 !getgen(0)->collectionattemptissafe() は、新しい世代の昇格が安全かどうかを参照します。現在の Old Gen の残りのスペースが Young GC によって昇格されたオブジェクトのサイズを収容するのに十分であるかどうかを判断します。 Young GC がどの程度昇格するかを事前に知ることは不可能であるため、ここでは、各 Young GC 昇格の平均サイズと、現在の Young GC が昇格する可能性のある最大サイズを比較します。 - //av_promo は各 YoungGC プロモーションの平均サイズ、max_promotion_in_bytes は現在可能な最大プロモーション サイズ (eden +現在使用されているスペースのサイズ)です。
- bool res = (利用可能 >= av_promo) || (利用可能 >= max_promotion_in_bytes);
5. メタ空間の状況に基づいて判断する ここでは、メタスペースが拡張される前に CMSClassUnloadingEnabled パラメータが構成されている場合に設定される、メタスペースの shouldconcurrent_collect フラグを主に確認します。この場合、CMS GC が実行されます。そのため、アプリケーションの起動直後に CMS GC が実行され、Old Gen 領域がわずかな割合を占めるという状況がよく発生し、非常に困惑することになります。実はこれが理由です。 要約する この記事では、CMS GC のフォアグラウンド コレクターとバックグラウンド コレクターのトリガー条件を整理します。フォアグラウンド コレクターのトリガー条件は比較的単純ですが、バックグラウンド コレクターのトリガー条件はより多く、5 つの主要な状況に分けることができます。それぞれの大きな状況には、いくつかの小さなトリガー分岐もあります。特に、UseCMSInitiatingOccupancyOnly パラメータが構成されていない場合は、トリガーの可能性がさらに多くなります。一般的に、CMS GC が確実に実行されるように、実稼働環境で UseCMSInitiatingOccupancyOnly パラメータを構成することを強くお勧めします。また、GC の原因をトラブルシューティングするのにも便利です。 |