代表的な面接トピック

JVM面接:長時間のGarbage Collection(GC)一時停止をどのように診断・解決するか?

一般難しい
Offer.cc 編集チーム公開日 更新日

質問

あるJavaサービスがHotSpot JDK 25上でG1を使用し、-Xms12g -Xmx12gで動作しています。キャッシュ機能をリリースした後、リクエストのp99が3〜5分ごとに120ミリ秒から2〜4秒へと跳ね上がるようになりました。スパイク発生時もCPUは飽和しておらず、GC後のOld領域の使用量は2時間で6.1 GiBから9.2 GiBに増加し、割り当てレートは約600 MiB/sから1.4 GiB/sに上昇しています。GCが停止を引き起こしているかをどのように証明し、高割り当てレート、生存セットの増加、巨大オブジェクト、明示的GC、非GCセーフポイントをどのように識別し、低リスクな改修・検証計画を策定しますか?

問題と適用シナリオ

低レイテンシが求められるJava APIが、HotSpot JDK 25上でG1および固定12 GiBヒープ(-Xms12g -Xmx12g)で稼働しています。キャッシュ機能のリリース後、リクエストのp99が3〜5分ごとに120ミリ秒から2〜4秒に悪化するようになりました。スパイク中、ホストのCPUは飽和していません。モニタリングでは、GC後のOld世代の使用量が2時間で6.1 GiBから9.2 GiBに増加し、アプリケーションのメモリ割り当てレートも約600 MiB/sから1.4 GiB/sに増加していることが示されています。チームはこのインシデントを「GCの問題」と判断し、まずヒープを24 GiBに拡張することを提案しています。

すべての数値は面接用の前提条件です。候補者は、レイテンシスパイクが実際にJVMの一時停止と一致しているかをどう証明するか、統合GCロギング、Java Flight Recorder(JFR)、および限定的なヒープ診断を使用して、高い割り当てレート、生存セットの増加、G1の巨大オブジェクト(Humongous Object)、明示的なSystem.gc()、および非GCセーフポイントをどう切り分けるか、そしてロールバック可能な改修・検証計画をどう作成するかを説明する必要があります。目標はコレクターのフラグを暗記して暗唱することではありません。症状から証拠、仮説、実験、そしてSLOに至る因果関係の連鎖を構築することです。

公開されているJava GCの面接資料では、Stop-the-WorldイベントとJVMの一時停止時間が中心的なトピックとして扱われます。Oracleによる長時間のGC一時停止に関するトラブルシューティングガイドでは、ヒープ不足、ヒープの断片化、OSのアクティビティ、明示的GCが直接網羅されています。これらの情報源から、本問は代表的なJVMパフォーマンス診断の設問となります。特定の企業に関する確実な帰属情報は公開されていないため、companyNameは空のままとしています。

面接官が見ているポイント

第1のシグナルは、候補者がチューニングを行う前にタイムラインを揃えているかどうかです。リクエストp99、JVMのGCログ、JFRイベント、コンテナのCPUスロットリング、ディスクイベント、およびホストのメトリクスには共通の時間軸が必要です。ヒープグラフの鋸歯状の波形はGCが発生したことを証明するだけであり、特定の3秒間のリクエストスパイクがGCによって引き起こされたことを証明するものではありません。逆に、G1はコンカレントな処理を大幅に行うため、長いGCサイクルが実行されていたとしても、そのサイクル全体でアプリケーションが停止していたわけではありません。

第2のシグナルは、単一の極端に長い一時停止と、総停止時間の過度な比率を区別できているかどうかです。前者は停止タイプやフェーズの所要時間、GC前後の生存データ、コピーされたバイト数、およびOSのタイミングを必要とします。後者は多くの場合、割り当てレートや収集頻度を追跡します。平均GC時間だけを見ていると、稀に発生する末尾の長い停止と、大量に発生する短い停止の両方を見落とすことになります。

第3のシグナルは、証拠に基づいて解決策を選択しているかどうかです。割り当て圧力に対しては、割り当てのホットスポットを特定する必要があります。GC後のOld世代ベースラインの上昇に対しては、不要なオブジェクトが到達可能なままになっていることを証明する必要があります。Humongous regionsの増加に対しては、G1リージョンの半分を超えるオブジェクトを追跡する必要があります。ログ上の原因がSystem.gc()である場合は、呼び出し元を特定する必要があります。「ヒープを増やす、目標停止時間を下げる、またはZGCに切り替える」というのは、あらゆる症状に適合する診断ではありません。

最後に、面接官は本番環境におけるリスク認識をテストしています。JFRのヒープ統計は追加のOld世代GCをトリガーします。Oracleはjcmd GC.class_histogramやヒープダンプを高負荷な操作として位置づけており、ヒープダンプはデフォルトでFull GCを要求します。優れた回答は、まずオーバーヘッドの低い証拠を収集し、レプリカ、オフピーク時、または制御された環境で高負荷な証拠を取得し、同等負荷のカナリアデプロイを使用して、スループット、CPU、またはメモリの許容できない犠牲を払わずに停止時間を短縮できたことを証明します。

回答前に明確にすべき質問

  • 具体的に何が「停止(stalled)」しているか? サーバーのハンドラーレイテンシなのか、クライアントのエンドツーエンドレイテンシなのか、すべてのJavaスレッドの処理が進んでいないのか、それとも一部のリクエストがキューイングされているだけなのか? スパイクは単一インスタンスに限定されているか、ロードバランサー、依存関係、またはネットワークでも同様のイベントが発生しているか?
  • ログとメトリクスを正確に関連付けることができるか? 同じインスタンスID、UTCタイムスタンプ、JVM稼働時間、およびリリースバージョンが必要です。ログに分単位のタイムスタンプしかない場合は、見た目が似ているだけの2つの曲線から結論を導き出す前に、可観測性を向上させる必要があります。
  • G1ログには何が含まれているか? 最低でも、GC ID、原因、停止タイプ、GC前後のヒープ状態、および所要時間を記録しておく必要があります。必要に応じてgc+heapgc+phasesgc+cpuを有効にします。プロセスのRSSやヒープ使用率のパーセンテージだけでは不十分です。
  • 同等の負荷下で、収集後のOld世代ベースラインは上昇し続けているか? キャッシュのウォームアップ後にプラトー(頭打ち)に達するのであれば、それが想定される生存セットの可能性があります。回収されるバイト数が減少しながら継続的に増加している場合は、オブジェクトの不要な保持またはメモリリークの可能性が高くなります。
  • コンテナとホストにはどのような制限が適用されているか? CPUクォータとスロットリング、スワップ、ページフォールト、メモリ圧力、ブロックするログI/O、およびNoisy Neighbor(ノイジーネイバー)を確認します。GCのウォールタイムが長いにもかかわらずCPU時間が極端に短い場合は、JVMのスケジュールが解除されたか、ページがスワップアウトされた可能性があります。
  • 今回のリリースで、どの割り当て関係および参照関係が変更されたか? キャッシュ容量、TTL、キーと値のサイズ、シリアライゼーションバッファ、バッチサイズ、並行性、およびThreadLocal、リスナー、キュー、静的コレクションによる参照保持を調査します。
  • レイテンシとスループットの目標は何か? 停止時間のp99と最大値、リクエストp99、スループット、エラー率、CPU、およびメモリの制限を定義します。-XX:MaxGCPauseMillisはG1に対する目標のヒントであり、すべての停止時間に対する厳格な上限ではありません。
  • 高負荷な証拠を安全に収集できるか? 本番インスタンスが1つしかない場合は、まずキャパシティを追加するかトラフィックを迂回(ドレイン)させてください。インシデント中に12 GiBのヒープをダンプし、その診断用のFull GCを元の障害と誤認してはなりません。

30秒の回答フレームワーク

「私はまずヒープサイズを変更することはしません。各リクエストスパイクをインスタンスとタイムスタンプ単位で-Xlog:gc*、JFRのjdk.GCPhasePauseイベント、CPUスロットリング、ページフォールト、および依存関係のレイテンシと関連付け、アプリケーションが実際にどれだけ停止していたかを測定します。次に、証拠を3つのグループに分けます。各一時停止のタイプとフェーズ、時間枠ごとの総停止時間比率、そして割り当てレート、昇格レート、GC後Old世代使用量、巨大領域(Humongous Region)の傾向です。

キャッシュによって割り当てが増加したものの収集後ベースラインが安定している場合は、JFRを使用して割り当てのホットスポットを特定し、一時オブジェクトを削減します。GC後Old世代が上昇し続けている場合は、クラスヒストグラムを比較し、制御されたレプリカでヒープダンプを取得して、ドミネーターツリーと参照保持パスを調査します。Full GCの前に退避失敗(Evacuation Failure)や多数の巨大領域が発生している場合は、大きな配列、バッファ、バッチを調査して分割します。原因がSystem.gc()である場合は、呼び出し元を特定します。すべての変更は、リクエストp99、停止p99および最大停止時間、総停止時間比率、割り当てレート、収集後生存セット、CPU、スループット、エラーを対象に、同等負荷のカナリア環境でテストしてから本番展開します。」

ステップごとの詳細解説

ステップ1:反証可能なタイムラインを構築し、GCが関与しているかを証明する。

レイテンシスパイクごとに、インスタンス、リクエストの時間枠、リリースバージョン、およびUTC時刻を記録します。これらを統合ログおよびGC IDごとのJFR一時停止イベントと関連付けます。ベースラインとなる起動設定により、タイムスタンプとタグが付与されたGCおよびセーフポイントのデータをローテーションファイルに保持できます。

text
-Xlog:gc*,safepoint:file=/var/log/app/gc-%p.log:time,uptime,level,tags:filecount=10,filesize=100m

これは診断用の例です。パス、保持期間、およびディスクの予算は環境に合わせて調整してください。JFRでは、jdk.GCPhasePauseの所要時間に注目しつつ、CPU負荷、スレッド、ソケット/ファイルI/O、および割り当てイベントも調査します。Oracleのガイドラインでは、コンカレントコレクターにおいて、総サイクル時間よりもアプリケーションが実際に停止していた時間の方が重要であると指摘されています。

3つの結果分岐を作成します。スパイクがJVMの一時停止と1対1で一致する場合、GCの根本原因分析に進みます。報告されたGC停止時間は短いがセーフポイント時間が長い場合、セーフポイントの理由と到達までの時間(Time to Safepoint)を調査します。どちらも一致しない場合は、CPUスロットリング、ロック、I/O、ネットワーク、およびダウンストリームサービスを調査します。これにより、GC仮説があらゆるレイテンシに対するデフォルトの言い訳になるのを防ぎ、反証可能になります。

ステップ2:単一のグラフではなく、メトリクスセットでGCの症状を記述する。

最低でも以下の時系列データを保持し、リリース前後で同一の負荷ウィンドウを比較します。

text
Requests: p50 / p95 / p99 / max, throughput, timeouts, error rate
Pauses: pause p50 / p95 / p99 / max, paused time per minute, pause cause
Heap: young / old usage, old-after-GC, reclaimed bytes, promotion rate
Allocation: bytes/s, top allocation sites by class and thread, inside/outside TLAB
G1: young / mixed / Full counts, evacuation failures, humongous regions
System: process CPU, GC CPU, CPU throttling, RSS, swap, major page faults, disk latency

used-before → used-after (heap-capacity)形式は1回の収集で何が回収されたかを示しますが、単一のデータポイントは傾向を示しません。同等の負荷下でGC後Old世代ベースラインが単調増加している場合は、生存セットの増加または昇格圧力(Promotion Pressure)を示唆します。ベースラインが安定しているにもかかわらず収集頻度が増加している場合は、高い割り当てレートを示していることが多くなります。gc+cpu=infoにおいて実時間がユーザー時間+システム時間を大幅に上回っている場合は、GCスレッドを追加する前に、OSのスケジューリング、クォータ、またはページングを調査してください。

ステップ3:証拠に基づいて5つの根本原因パスを切り分ける。

  1. 高い割り当てレート: GC後Old世代は安定しているが、Young GCの停止回数と1分あたりの停止時間が増加している。JFRの割り当てイベントは、キャッシュキーの生成、シリアライゼーション、または一時コレクションを示します。コピー処理を削減し、ライフサイクルが明確なバッファを再利用し、バッチサイズを制御します。証拠なしに無闇にオブジェクトプールや共有状態を導入しないでください。
  2. 生存セットの増加またはメモリリーク: GC後Old世代が上昇し続け、各収集での回収量が減少している。複数の時点でクラスヒストグラムを比較します。ヒープダンプはオフピーク時のレプリカまたはトラフィック再生環境でのみ取得し、保持サイズ(Retained Size)、ドミネーターツリー、GCルートを使用して、正当なキャッシュと制限のない保持を区別します。容量で頭打ちになるキャッシュはサイジングの問題です。期限切れのオブジェクトが参照されたままになっている場合はリークを示します。
  3. G1巨大オブジェクト(Humongous Object): G1はリージョンサイズの半分以上のオブジェクトを巨大オブジェクトとして扱い、Old世代の連続したリージョンに直接配置します。Humongous regions: X → Yが高いままの場合、大きなbyte[]char[]、圧縮ブロック、またはバッチを追跡し、まず個々のオブジェクトやバッチサイズを縮小します。リージョンサイズの変更は、リージョン数や収集の粒度も変化させるため、測定を行った後の実験としてのみ扱います。
  4. コンカレントマーキングの遅延または退避失敗(Evacuation Failure): ログに退避先スペースの不足や退避できないリージョンが表示されている場合、最悪のシナリオはヒープ全体を対象とするFull GCです。Oldリージョンへの割り当てや昇格を減らし、コンカレントマーキングに十分なヘッドルームを与え、ヒープ容量と生存セットの間の安全マージンを確認します。MaxGCPauseMillisを下げるだけでは、各収集の作業量が減り、システムが枯渇に近づくだけになる可能性があります。
  5. 明示的GCまたは非GCセーフポイント: GCの原因がSystem.gc()である場合は、コールスタック、依存関係の設定、および運用コマンドを使用して発生源を突き止め、呼び出しの削除、ライブラリの設定、またはタスクの分離を行います。DisableExplicitGCは、セマンティクスへの影響を確認した後にのみ使用してください。セーフポイント時間は長いがGC停止時間が短い場合は、コレクターを変更するのではなく、逆最適化(Deoptimization)、クラスの再定義、その他のVM操作など、実際の原因を調査します。

ステップ4:診断ツールを階層化し、観測リスクを制御する。

まずは継続的に利用可能で低コストなレイヤーから始めます。アプリケーションのSLI、統合GC/セーフポイントログ、および標準JFRです。稼働中の確認には、同一ホスト上で同一の実効ユーザーとしてjcmd PID JFR.startJFR.check、およびJFR.dumpを実行します。まずjcmd PID help COMMANDを使用して、そのJVMでサポートされているオプションを確認してください。

次に高コストなレイヤーに移行します。OracleはGC.class_histogramを高負荷(high impact)としています。GC.heap_dumpも高負荷であり、デフォルトでFull GCを要求します。JFRのヒープ統計は開始時と終了時に追加のOld世代GCをトリガーするため、レイテンシ調査中にデフォルトでヒープ統計を有効にしてはなりません。トラフィックを迂回させるかレプリカ上で問題を再現し、ディスク容量と機密データの取り扱いを確認した上で、ヒストグラムやダンプを取得します。安定した負荷間隔を空けて取得した2つのキャプチャを比較する方が、単一の最大オブジェクトリストよりも増加の原因を正確に説明できます。

ステップ5:コード、キャパシティ、コレクター設定を変更する際は、単一の因果関係を保つ。

新しいキャッシュから調査を始めます。上限は設定されていますか? TTLによって実際にエントリが期限切れになっていますか? 最大重みはエントリ数ではなくバイト単位で測定されていますか? 各値で大きな配列がコピーされていませんか? 並行したキャッシュミスによって同じ値が独立して構築されていませんか? 候補となるコード修正には、最大重みの制限、同一キーロードの統合、ストリーミングシリアライゼーション、バッチサイズの縮小、または一時オブジェクトへの参照の早期解放が含まれます。各実験では1つの主要な変数のみを変更します。

ヒープを大きくすると収集間隔を伸ばすことができますが、無制限な保持を隠蔽し、コンテナのメモリリスクを高め、最終的に処理しなければならない生存データ量を増加させる可能性もあります。IHOP、Young世代のサイジング、リージョンサイズ、または目標停止時間を変更する前に、そのフラグが変更すると期待されるログ上のフェーズやリソース不足を特定してください。ZGCなどのコレクターへの移行は、新たなスループット、CPU、ヒープヘッドルーム、コンテナ、および運用の検証を必要とするアーキテクチャ上の実験です。インシデント時の最初のコマンドではありません。

ステップ6:同等負荷のカナリアと反事実的証拠をもってインシデントをクローズする。

本番環境と同等のリクエスト、オブジェクトサイズ、キャッシュヒット率、および並行性を、ベースラインバージョンと修正バージョンに対して再現します。カナリアテストは、元の3〜5分のスパイクサイクルを複数回カバーし、キャッシュのコールドスタートと定常状態の両方を含める必要があります。テスト前に受け入れ基準を定義します。例えば、リクエストp99が200ミリ秒未満、GC停止p99が100ミリ秒未満かつ最大500ミリ秒未満、1分あたりの停止時間が1%未満、ウォームアップ後にGC後Old世代が増加しなくなること、スループット、CPU予算、OOM、エラーの悪化がないことなどです。

次に、反事実的な検証を行います。キャッシュをロールバックすると、割り当てレートとスパイクの両方が元に戻るか? キャッシュの重みを制限するだけでGC後Old世代が安定するか? バッチサイズのみを縮小することで巨大領域が減少するか? 予測されたメトリクスが連動して改善される場合、その改修策は根本原因と因果関係を持っています。段階的にロールアウトし、自動ロールバック基準を維持します。

高品質な回答例

「現在のデータからGCを調査する価値は十分にありますが、GCが2〜4秒のリクエストスパイクの原因であると確定したわけではありません。私はまず、リクエストSLI、-Xlog:gc*,safepoint、JFRのjdk.GCPhasePauseイベント、CPUスロットリング、ページフォールト、依存関係のレイテンシをインスタンスおよびUTC時刻で照合します。スパイクが停止と一致する場合、1回の長い停止なのか、蓄積された多数の短い停止なのかを区別します。セーフポイント時間のみが長い場合は、セーフポイントの原因を追究します。どちらも一致しない場合は、GCの調査から離れます。

今回のリリースにより、割り当てが600 MiB/sから1.4 GiB/sに増加し、GC後Old世代は6.1 GiBから9.2 GiBに上昇しました。ここから少なくとも2つの仮説が成り立ちます。キャッシュが大量の一時割り当てを発生させたこと、そしてキャッシュまたは関連オブジェクトが生存セットを拡大させたことです。Young、Mixed、Fullの原因、GC前後のヒープ、昇格、退避失敗、および巨大領域を調査します。JFRの割り当てイベントによってクラス、スレッド、呼び出し元を特定できます。複数時点のヒストグラムは、どのクラスが増加し続けているかを示します。ヒープダンプは高負荷でありデフォルトでFull GCを要求する可能性があるため、トラフィックをドレインしたレプリカか再生環境でのみ取得します。

収集後のベースラインが安定しているにもかかわらずYoung GCの停止が頻発している場合は、キャッシュキー構築、シリアライゼーション、一時コレクションにおける割り当てを削減します。ベースラインが上昇し続けている場合は、保持サイズとGCルートパスを使用して、境界のあるキャッシュとリークを区別します。大きな配列がG1リージョンの半分を超え、巨大領域の使用が増加している場合は、バッファまたはバッチを分割します。原因がSystem.gc()である場合は呼び出し元を特定します。実時間がGCのCPU時間を大幅に超えている場合は、クォータ、スワップ、ホストの競合を調査します。

ヒープを24 GiBに増やすことを恒久的な修正とはみなしません。キャッシュのコールドスタートと元のスパイクサイクルを複数回含む同等負荷のカナリア環境で、候補となる各変更を個別にテストします。最終判定基準には、リクエストp99、停止p99および最大停止時間、総停止時間比率、割り当てレート、GC後Old世代、巨大領域、スループット、CPU、RSS、エラー、OOMが含まれます。これをクリアした後に初めて、ロールバック手順を確保した上で段階的にトラフィックを増やします。」

よくある間違い

  • 間違い:鋸歯状のヒープグラフを見ただけでGCが原因だと断定する → 失敗する理由:グラフは収集が行われたことの証明に過ぎず、単一インスタンス上でリクエストスパイクとStop-the-World停止が重なっていたことの証明にはならない → 修正方法:リクエスト、ログ、JFRをインスタンス、GC ID、単一のタイムラインで関連付ける。
  • 間違い:平均GC時間だけを見る → 失敗する理由:平均値は単一の3秒の末尾イベントを隠蔽し、多数の短い停止による累積コストを無視してしまう → 修正方法:停止時間のパーセンタイル、最大値、1分あたりの停止時間、および原因を測定する。
  • 間違い:GCサイクルの所要時間をアプリケーション停止時間として扱う → 失敗する理由:G1のマーキング作業の多くはアプリケーションと並行して実行できるため → 修正方法:実際の停止イベントとリクエストSLIを使用する。
  • 間違い:インシデント中にヒープを12 GiBから24 GiBに増やす → 失敗する理由:生存セットに有効な上限があることを証明せずに無制限な参照保持を隠蔽し、メモリリスクを高める可能性がある → 修正方法:割り当てレートと収集後の生存セット増加を切り分け、ロールバック可能なキャパシティ実験を実施する。
  • 間違い:唯一の本番インスタンスで直ちにヒープダンプを取得する → 失敗する理由:このコマンドは高負荷であり、デフォルトでFull GCをトリガーし、巨大なファイルを書き出し、機密データを露出させるため → 修正方法:トラフィックをドレインし、レプリカまたは制御された再生環境で収集する。
  • 間違い:大きなオブジェクトに気づいた後、すぐにG1リージョンサイズを変更する → 失敗する理由:大きなオブジェクトがリージョンの半分の閾値を超えていない可能性があり、このフラグはヒープ全体のリージョン粒度を変更してしまうため → 修正方法:gc+heapと割り当ての証拠で巨大オブジェクトを確認し、実験結果を比較する。
  • 間違い:-XX:MaxGCPauseMillis=50をSLAとして扱う → 失敗する理由:これは目標のヒントであり、G1はリアルタイムコレクターではないため → 修正方法:実際の停止分布を検証し、目標、スループット、ヒープヘッドルームのトレードオフを計算する。
  • 間違い:System.gc()が表示されたら即座に明示的GCをグローバルに無効化する → 失敗する理由:依存ライブラリや運用ワークフローがそのセマンティクスに依存している可能性があるため → 修正方法:削除、設定変更、または分離を行う前に、呼び出し元とその意図を特定する。
  • 間違い:コレクター変更後にp99の低下のみを確認する → 失敗する理由:停止時間の短縮は、CPU使用率の上昇、スループットの低下、またはメモリ消費の増加と引き換えになっている可能性があるため → 修正方法:同一負荷下でレイテンシ、スループット、CPU、RSS、エラー、および復旧性をテストする。

フォローアップの質問と回答

フォローアップ1:メモリリークと通常のキャッシュウォームアップをどのように区別しますか?

プロセス起動直後の初動の傾きだけでなく、同等の負荷下で複数回のOld世代GCを経た後の生存セットを調査します。通常の制限付きキャッシュは、最大重みと安定したヒット率に達した後にプラトー(平坦化)に達し、エビクション(追い出し)やTTL期限切れが観測できるようになります。リークが発生している場合、ビジネス上の価値がないオブジェクトがGCルートから到達可能なまま残り、ベースラインが上昇し続けます。複数時点のクラスヒストグラムを使用して増加しているクラスを特定し、制御されたヒープダンプでドミネーター、保持サイズ、参照パスを調査します。仮にキャッシュがいずれ頭打ちになるとしても、12 GiBヒープ内で9.2 GiBを占有していると、コンカレントマーキングやバーストのためのヘッドルームが不十分になる可能性があり、キャパシティ設計の問題となります。

フォローアップ2:なぜ最初にMaxGCPauseMillisを下げないのですか?

この設定はG1が1回の収集で試行する作業量をガイドするものであり、強制的な上限ではありません。生存セットが大きすぎる場合、コンカレントマーキングが追いつかない場合、またはコンテナが十分なCPUを提供できない場合、目標値を下げると各Mixed GCでの回収量が減り、頻度が増加し、退避失敗(Evacuation Failure)に近づく可能性があります。まずフェーズのタイミング、ヒープヘッドルーム、割り当てレートまたは昇格レートから仮説を立て、同一負荷下で1つのフラグ変更を検証してください。

フォローアップ3:GCログで実時間が長いのにユーザー時間とシステム時間が短い場合、何が示唆されますか?

これは、GCスレッドがウォールクロック(実時間)の間ずっとCPUを使用できていなかったことを示唆しています。考えられる原因には、コンテナのCPUスロットリング、ホストのCPU競合、スワップまたはメジャーページフォールト、仮想化による一時停止、ログI/Oのブロックなどがあります。cgroupのクォータとスロットル時間、ランキュー(実行キュー)、ページフォールト、スワップ、ディスクレイテンシ、コロケーションホストのイベントを調査してください。並列GCスレッドを追加すると競合が悪化する可能性があるため、まず外部リソース供給に関する証拠に対処してください。

フォローアップ4:G1からZGCへの移行を検討するのはどのような場合ですか?

サービスに明確な超低レイテンシの目標があり、割り当てと生存セットの増加が制御されており、目標ヒープと負荷においてG1が依然として停止時間SLOを達成できず、チームが追加のCPU、ヒープヘッドルーム、JDK互換性、および運用を検証できる場合に検討します。コールドスタート、定常状態、バースト、障害復旧、コンテナ制限を含む本番相当のトラフィックでA/Bテストを実施してください。コレクターの移行はキャパシティとランタイムモデルの選択であり、オブジェクトリークや上限のないキャッシュの修正を代替するものではありません。

フォローアップ5:修正が単にスパイクを遅らせただけでなく、恒久的に持続することをどう証明しますか?

カナリアテストを十分に長く実行し、元のスパイクサイクルを複数回、キャッシュの定常状態、および予想されるピークをカバーします。GC後Old世代の傾き、時間あたりのFull GCおよび退避失敗の回数、割り当てレート、巨大領域、総停止時間比率、リクエストSLIを比較し、ヘッドルームを確認するために予測ピークを超える負荷をテストします。ヒープを増やしたことで最初のスパイクが5分から10分に伸びただけで、ベースラインの傾きが変わっていない場合、その改修は失敗です。

公開情報ソース

関連する質問