オンライン FullGC トラブルシューティング プラクティス — オンラインの問題のトラブルシューティング方法を説明 | JD Cloud 技術チーム

著者: JD Technology の Han Kai

1. 問題の発見とトラブルシューティング

1.1 問題の原因を見つける

問題の原因は、jdos コンテナーの CPU アラームを受信し、CPU 使用率が 104% に達したことです。

画像-20230421152602849

マシン ログを観察すると、現時点でバッチ タスクを実行しているスレッドが多数あることがわかります。通常、バッチ実行タスクは低 CPU で高メモリ タイプであるため、現時点で CPU 使用率が高いのは FullGC が原因であると考えられます (以前にも同様の状況があり、担当者に伝えたところ問題は解決しました)。アプリケーションを再起動する必要があります)。

Taishan でマシンのメモリ使用量を確認します。

画像-20230421152933510

CPU 使用率は確かに高いことがわかります **が、メモリ使用率は高くなく、正常範囲内の 62% にすぎません。

ここにたどり着くのは実際には少し混乱します.この時点でメモリがいっぱいになっているのは当然です.

その後、トラフィックの突然の流入などの他の指標に基づいて、突然の大量の呼び出しによって cpu が jsf インターフェイスによって占有されていたのではないかと疑ったため、メモリ使用率は高くはありませんでしたが、徐々に増加しました。後で除外しました。実はここでちょっと途方にくれて、現象が推測と一致せず、CPUだけが増えてメモリが増えないということで、一方的なCPUの増加は何が原因なのか?** その後、私はこの方向で長い間チェックインしましたが、拒否されました。

後でふと気づいたのですが、もしかしたらモニタリングに「問題」があるのではないでしょうか?

つまり、監視に問題があるはずですが、ここでの監視はマシンの監視であり、JVM の監視ではありません。

JVM によって使用される CPU はマシンに反映されますが、JVM のヒープ メモリの使用率が高いことはマシン上ではあまり明確ではありません。

次に、sgm に移動して、対応するノードの jvm 関連の状況を確認します。

画像-20230421154928774

古い世代のヒープ メモリが実際にいっぱいになり、その後クリーンアップされていることがわかります。このときの CPU 使用率を確認することも、GC 時間に対応している可能性があります。

この時点で、問題の原因がフル GC であると判断できます。

1.2 FULL GC の原因を見つける

最初に gc 前後のヒープ メモリ スナップショットをダンプしました。

次に、JPofiler を使用してメモリ分析を行います。(JProfiler はヒープ メモリ分析ツールであり、オンライン jvm に直接接続して関連情報をリアルタイムで表示できます。また、ダンプされたヒープ メモリ スナップショットを分析して、特定の瞬間のヒープ メモリの状況を分析することもできます)

まず、ダンプしたファイルを解凍し、接尾辞 name を変更して.binから開きます。(Xingyun に付属のダンプ ツールを使用するか、マシンに移動してコマンドで手動でファイルをダンプすることができます)

画像-20230421155755209

最初に [最大のオブジェクト] を選択して、その時点でヒープ メモリ内にある最大のオブジェクトを表示します。

図からわかるように、4 つの List オブジェクトがほぼ 900MB のメモリを占有しており、最大ヒープ メモリが 1.3GB しかないことがわかりました. したがって、他のオブジェクトを追加すると、古い世代が簡単にいっぱいになり、フルGCの問題。

画像-20230421160135305

見たいオブジェクトとして、最大のオブジェクトの1 つを選択します。

この時点で、対応するラージ メモリ オブジェクトに対応する場所をすでに特定できます。

画像-20230421160241646

実際、これまでのところ大まかに問題を突き止めることができました.それでも不明な場合は、特定のオブジェクト情報を確認できます.方法は次のとおりです。

画像-20230421160532920

大きな List オブジェクトには実際には多くの Map オブジェクトが含まれており、各 Map オブジェクトには多くのキーと値のペアがあることがわかります。

ここでは、関連する属性情報もマップで確認できます。

次のインターフェイスで関連情報を直接確認することもできます。

画像-20230421160715617

次に、一番下までクリックして、対応するプロパティを表示します。

これまでのところ、コード内の大きなオブジェクトの場所を理論的に見つけました。

2. 問題解決

2.1 コード内の大きなオブジェクトの場所と問題の根本原因を見つける

まず、上記のプロセスに従って、対応する位置とロジックを見つけます

私たちのプロジェクトの一般的なロジックは次のとおりです。

  1. まず、ユーザーがアップロードした Excel サンプルが解析され、上記の変数である List 変数としてメモリに読み込まれます。20w のサンプルの場合、この時点でいくつかのフィールドがあり、約 100MB のスペースを占有します。
  2. 次に、ループ ユーザー サンプルをトラバースし、ユーザー サンプルのデータに従って追加の要求データを追加し、このデータに基づいて関連する結果を要求します。この時点で、フィールド数はa+nで、占有スペースはすでに約200MBです。
  3. ループが完了した後、この 200 MB のデータをキャッシュに格納します。
  4. Excel の生成を開始し、キャッシュから 200 MB のデータを取り出し、最初のサンプル フィールドを取り出し、以前に記録した a フィールドに従って Excel に入力します。

フローチャートで表すと:

jvmgc-Page-1.drawio

特定のトラブルシューティングの問題のいくつかの写真と組み合わせる:

画像-20230421172512115

現象の 1 つは、各 gc の後の最小メモリが徐々に増加していることです。これは、上記の手順の 2 番目のステップに対応して、メモリが徐々に拡大しています。

結論

ユーザーがアップロードしたExcelサンプルをメモリにロードし、List<Map<String, String>>構造体として保存します.まず、この方法で保存された20MBのExcelファイルは、拡張され、約120MBのヒープメモリを占有します.このステップは、大量のヒープメモリを占有します.論理的な理由から、jvm には大きなオブジェクト メモリが 4 ~ 12 時間存在するため、タスクが多すぎると jvm ヒープ メモリが簡単にいっぱいになります。

以下は、HashMap を使用するとメモリの拡張が発生する理由のリストです.主な理由は、ストレージ スペースの効率が比較的低いためです。

Long オブジェクトはメモリ計算を占有します。HashMap<Long, Long> 構造では、Key と Value に格納された 2 つの long 整数データのみが有効なデータで、合計 16 バイト (2×8 バイト) です。これら 2 つの long 整数データが​​ java.lang.Long オブジェクトにパッケージ化されると、MarkWord が 8 バイト、Klass ポインターが 8 バイト、データを格納するための long 値が 8 バイトになります (パッケージ オブジェクトは 24 文字を占めます)。

次に、これら 2 つの Long オブジェクトが Map.Entry を形成した後、追加の 16 バイトのオブジェクト ヘッダー (8 バイトの MarkWord + 8 バイトの Klass ポインター = 16 バイト)、および 8 バイトの次のフィールドと 4 バイトのハッシュ フィールドがあります。 int 型 (8 バイトのネクスト ポインター + 4 バイトのハッシュ フィールド + 4 バイトのパディング = 16 バイト) の場合、アラインするために 4 バイトの空白のパディングも追加する必要があり、最終的に HashMap にエントリがあります。このように 2 つの long 整数を加算した 8 バイトの参照、実際のメモリ消費量は (Long(24byte)×2)+Entry(32byte)+HashMapRef(8byte)=88byte であり、空間効率は有効なデータを分割したものです。つまり、16 バイト/88 バイト = 18% です。

—— 「Java仮想マシンを深く理解する」5.2.6

以下は、アップロードしたばかりの Excel からダンプされたヒープ メモリ オブジェクトで、128 MB のメモリを占有しますが、アップロードされた Excel は実際には 17.11 MB しかありません。

画像-20230423145825354

画像-20230423145801632

容量効率 17.1mb/128mb≒13.4%

2.2 この問題の解決方法

上記のプロセスが妥当かどうかは別として、解決策は一般的に 2 つのカテゴリに分けることができます. 1 つは、根本原因を解決すること、つまり、オブジェクトを jvm メモリに配置するのではなく、キャッシュに格納することです.がメモリにない場合、大きなオブジェクトの問題は自然に解決されます。もう 1 つは症状を処理することです。つまり、大容量メモリ オブジェクトを縮小して、日常的な使用シナリオでフル GC の問題が頻繁に発生しないようにします。

どちらのアプローチにも長所と短所があります。

2.2.1 過激な治療: 彼を記憶に留めない

解法ロジックも非常にシンプルで、例えばデータをロードする際、サンプルロードデータに合わせて一つずつredisキャッシュに格納し、あとはサンプルに何個あるのかを知り、取り出すだけです。量の順序に従ってキャッシュからデータを取得します。問題を修正します。

利点: この問題は根本的に解決でき、将来的にはこの問題は基本的に存在しない. どんなにデータ量が大きくても、対応するredisリソースを追加するだけでよい.

短所: まず第一に、それは多くの redis キャッシュ スペースの消費を増加させます. 第二に、表示を考慮すると、私たちのプロジェクトでは、ここのコードは古くてわかりにくく、変更には大量の作業と回帰テストが必要です.

Fuxi 操作の背景 fullgc-page 2.drawio

2.2.2 保守的な扱い: データ量の削減

上記の 2.1 のプロセスを分析すると、まず、3 番目のステップは完全に不要であり、最初にキャッシュに格納されてから取り出されるため、追加のキャッシュ スペースが必要になります。(推測は歴史的な問題なので、ここでは掘り下げません)。

次に、2 番目のステップでは、余分なフィールド n はリクエストの終了後に役に立たないため、リクエストの終了後に不要なフィールドを削除することを検討できます。

このときの解決策も 2 つあります. 1 つは不要なフィールドのみを削除してマップのサイズを縮小し、生成された Excel にパラメーターとして渡すことです.もう 1 つはマップの直接削除の完了を要求することです.、その後、ユーザーがアップロードした Excel の読み取り Excel サンプルを生成するときに再起動します。

利点: 小さな変更、複雑すぎる回帰テストは不要

短所: 非常に大量のデータの場合でも、フル GC が発生する可能性があります

jvmgc-page 3.drawio

具体的な実施方法は拡大しない。

実装の 1 つ

//获取有用的字段
String[] colEnNames = (String[]) colNameMap.get(Constant.BATCH_COL_EN_NAMES);
List<String> colList = Arrays.asList(colEnNames);
//去除无用的字段
param.keySet().removeIf(key -> !colList.contains(key));

3. 思考を広げる

まず、本記事のモニター映像は当時のシーンを再現する際によく見かける人工GCです。

CPU 使用率グラフでは、CPU 使用率の上昇時間は確かに gc の時間と一致していることがわかりますが、その時のシーンでの 104% の CPU 使用率は表示されませんでした

画像-20230423103730420

画像-20230423103800435

実際、直接的な理由は比較的単純です。システムには完全な gc がありますが、頻繁に表示されるわけではないからです。

範囲が狭く周波数が低い完全な GC では、システムの CPU が急上昇することはありません。

では、その時のシーンの理由は何でしたか?

画像-20230423105534963

上で述べたように、ヒープ メモリ内の大きなオブジェクトは、タスクが進行するにつれて徐々に拡大するため、十分なタスクがあり、時間が十分に長い場合、使用可能なスペースは、完全な gc ごとに小さくなる可能性があります。使用可能なスペースがある程度小さくなると、フル gc が完了するたびに、スペースがまだ十分でないことがわかり、次の gc がトリガーされ、最終結果で gc が頻繁に発生し、 CPU 周波数が急上昇することはもうありません。

4. トラブルシューティングのまとめ

  • オンラインの CPU 使用率が高すぎる状況に遭遇した場合、まず問題がフル gc によるものかどうかを確認することができます.jvm の監視に注意するか、jstat 関連のコマンドを使用して確認してください. マシンのメモリ監視に惑わされないでください。
  • 問題の原因が gc であると判断された場合は、JProfiler を介してオンライン jvm に直接接続するか、dump を使用してヒープ スナップショットを保存し、オフラインで分析することができます。
  • 最初に、最大のオブジェクトを見つけることができます。これは通常、大きなオブジェクトによって引き起こされる完全な gc です。もう 1 つの状況は、4 つの大きなオブジェクトがあることはそれほど明白ではありませんが、比較的バランスのとれた 12 個ほどの 50MB オブジェクトである可能性もあります。
  • 上記のツールを使用して、問題のあるオブジェクトを見つけ、そのスタックに対応するコードの場所を見つけ、コード分析を通じて問題の具体的な原因を見つけ、他の現象を通じて推測が正しいかどうかを推測し、問題の本当の原因を見つけます。
  • 問題の原因に基づいて問題を修正します。

もちろん、上記はそれほど複雑なトラブルシューティングの状況ではありません.異なるシステムには異なるメモリ条件が必要です.特定の問題を詳細に分析する必要があります.この問題から学ぶことができるのは、問題をトラブルシューティングして解決する方法です.

{{o.name}}
{{m.name}}

おすすめ

転載: my.oschina.net/u/4090830/blog/8704836