Java オンラインのトラブルシューティング

オンラインの障害には、主に CPU、ディスク、メモリ、およびネットワークの問題が含まれます。ほとんどの障害には複数のレベルの問題が含まれている可能性があるため、トラブルシューティングを行う際は 4 つの側面を順番に確認してください。同時に、jstack や jmap などのツールは、問題の 1 つの側面に限定されず、基本的には、問題は df、free、上位 3 つ連続し、その後、jstack と jmap が順番にサービスを提供し、特定の問題を分析することができます詳細。

CPU

一般的に言えば、最初に CPU の問題のトラブルシューティングを行います。多くの場合、CPU 例外はより適切に配置されます。理由には、ビジネス ロジックの問題 (無限ループ)、頻繁な gc (これは以下の GC の章に含まれています)、およびコンテキスト スイッチが多すぎることが含まれます。最も一般的なのはビジネス ロジック (またはフレームワーク ロジック) が原因であることが多く、jstack を使用して、対応するスタックの状況を分析できます。

jstack

最初にpsコマンドを使用して、対応するプロセスの pid を見つけます (ターゲット プロセスが複数ある場合は、top を使用してどれがより多くを占めるかを確認できます)。

ps

次に、top -H -p pidを使用して、CPU 使用率が比較的高いスレッドをいくつか見つけ、最も占有されている pid を 16 進数のprintf '%x\n' pidに変換してnid を取得します。

トップ -H -p pid
printf '%x\n' pid

次に、対応するスタック情報を jstack で直接見つけます

jstack pid |grep 'nid' -C5 –color

ここに画像の説明を挿入

nid 0x42 のスタック情報が見つかったことがわかります。あとは、注意深く分析するだけです。
もちろん、jstack ファイル全体を分析する方が一般的ですが、通常は、BLOCKED は言うまでもなく、WAITING と TIMED_WAITING の部分に注意を払います。
以下のコマンドでjstackの全体的な状態を把握できますが、WAITINGなどが多すぎる場合は問題があると考えられます。

猫jstack.log | grep “java.lang.Thread.State” | 並べ替え -nr | ユニーク -c

コンテキストスイッチ

コンテキストの問題が頻繁に発生する場合は、 vmstatコマンドを使用して表示できます。
ここに画像の説明を挿入

より詳細なコマンドの場合、パフォーマンス データを 3 秒間隔で 10 回連続して収集します。

vmstat 3 10

cs (コンテキスト スイッチ) 列は、コンテキスト スイッチの数を表します。特定のpidを監視したい場合は、使用できます

pidstat -w pid

ここに画像の説明を挿入

cswch と nvcswch は、自発的および非自発的な切り替えを表します。

ディスク

ディスクの問題は、CPU と同じくらい基本的なものです。まず、ディスク容量に関しては、次のコマンドを直接使用してファイル システムのステータスを表示します。

df-hl

ここに画像の説明を挿入

多くの場合、ディスクの問題はパフォーマンスの問題です。次のコマンドで分析できます

iostatiostat -d -k -x

ここに画像の説明を挿入

最後の列 %util では、各ディスクの書き込みレベルを確認できます。rrqpm/s と wrqm/s はそれぞれ読み取り速度と書き込み速度を示しており、通常、問題のある特定のディスクを特定するのに役立ちます。

しかし、ここで得られるのは tid です。これを pid に変換したいので、readlink を介して pid を見つけることができます。

readlink -f /proc/*/task/tid/…/…

ここに画像の説明を挿入

pid を見つけた後、このプロセスの特定の読み取りおよび書き込み条件を確認できます。

猫 /proc/pid/io

ここに画像の説明を挿入

特定のファイルの読み取りおよび書き込み条件を決定する

lsof -p pid

ここに画像の説明を挿入

メモリー

メモリの問題のトラブルシューティングは、CPU よりも面倒であり、より多くのシナリオがあります。主に、OOM、GC の問題、およびオフヒープ メモリが含まれます。一般的に言えば、最初にfreeコマンドを使用して、メモリのさまざまな状態を確認します。
ここに画像の説明を挿入

ヒープメモリ

メモリの問題のほとんどは、ヒープ メモリの問題でもあります。外観は主に OOM と StackOverflow に分けられます。

OOM

JMV、OOM でのメモリ不足は、次のカテゴリに大別できます。

  • Exception in thread “main” java.lang.OutOfMemoryError: Cannot to create new native thread This means that there is not enough memory space to allocate a java stack to the thread. 基本的に、次のようなスレッド プール コードに問題があります。シャットダウンするのを忘れているので、まず jstack または jmap を使用してコード レベルで問題を探す必要があります。すべてが正常であれば、JVM は Xss を指定することでシングル スレッド スタックのサイズを減らすことができます。さらに、システム レベルでは、/etc/security/limits.confnofile と nproc を変更することで、スレッドの OS 制限を増やすことができます。
  • Exception in thread "main" java.lang.OutOfMemoryError: Java heap space は、ヒープのメモリ使用量が -Xmx によって設定された最大値に達したことを意味します。これは、最も一般的な OOM エラーです。解決策は、最初にコード内でそれを見つけ、メモリ リークがあることを疑い、jstack と jmap を使用して問題を特定することです。正常であれば、Xmx の値を調整してメモリを拡張する必要があります。
  • 原因: java.lang.OutOfMemoryError: Meta space means that the memory usage of the metadata area has received the maximum value set by XX:MaxMetaspaceSize. トラブルシューティングの考え方は上記と一致しています. パラメータは XX:MaxPermSize で調整できます ( here 1.8より前の永久世代は言うまでもありません)。

スタックオーバーフロー

スタック メモリ オーバーフロー、これはよく見られます。スレッド「main」での例外 java.lang.StackOverflowError は、スレッドスタックが必要とするメモリが Xss の値よりも大きいことを示しており、調査も最初に実行されます. Xss を通じてパラメーターを調整しますが、調整が大きすぎる場合は、 OOM を引き起こす可能性があります。

jmap

上記のコードの OOM と StackOverflow のトラブルシューティングには、一般的に JMAP を使用します。

jmap -dump:format=b,file=ファイル名 pid

ダンプ ファイルをエクスポートするには、mat (Eclipse メモリ分析ツール) などのダンプ分析ソフトウェアを使用して、分析用のダンプ ファイルをインポートします. 通常、メモリ リークの問題について Leak Suspects を直接選択することができ、mat はメモリ リークの提案を提供します. または、Top Consumers を選択して、最大のオブジェクト レポートを表示します。スレッドに関する質問は、スレッドの概要を選択して分析できます。さらに、Histogram クラスの概要を選択して、自分でゆっくりと分析したり、関連するチュートリアルを mat で検索したりできます。
ここに画像の説明を挿入

ここに画像の説明を挿入

日常の開発では、コード メモリ リークは比較的一般的であり、隠れているため、開発者は詳細に注意を払う必要があります。たとえば、毎回新しいオブジェクトが要求され、オブジェクトの作成が何度も繰り返される、ファイル ストリーム操作が実行されるが正しく閉じられない、gc を手動で不適切にトリガーする、ByteBuffer キャッシュの割り当てが不合理であるなどの理由で、コード OOM が発生します。
一方、起動パラメータに次のコマンドを指定して、OOM 時にダンプ ファイルを保存できます。

-XX:+HeapDumpOnOutOfMemoryError

オフヒープ メモリ

オフヒープ メモリ オーバーフローが発生した場合は、非常に残念です。まず、オフヒープメモリオーバーフローの性能は、物理常駐メモリが急激に増大することです.エラーが報告される場合は、使用方法に依存します.Nettyの使用が原因である場合、 OutOfDirectMemoryErrorエラーが表示される場合があります.エラー ログ直接 DirectByteBuffer の場合、OutOfMemoryError が報告されます: Direct buffer memory
ヒープ外メモリ オーバーフローは、多くの場合、NIO の使用に関連しています. 一般に、最初に pmap を介してプロセスによって占有されているメモリを確認します

pmap -x pid | 並べ替え -rn -k3 | 頭 -30

この段落は、pid の逆順に対応する上位 30 のメモリ セグメントを表示することを意味します。ここで、一定期間後にコマンドを再度実行して、メモリの増加を確認したり、通常のマシンと比較して疑わしいメモリ セグメントがどこにあるかを確認したりできます。

ここに画像の説明を挿入

しかし、実際には、上記の操作は特定の問題を特定するのが難しく、エラー ログ スタックを見て、疑わしいオブジェクトを見つけ、その回復メカニズムを把握し、該当するオブジェクトを分析することが重要です。たとえば、DirectByteBuffer がメモリを割り当てる場合、完全な GC または手動の system.gc をリサイクルする必要があります (したがって、-XX:+DisableExplicitGC を使用しないことをお勧めします)。実際、DirectByteBuffer オブジェクトのメモリを追跡し、jmap -histo:live pidを介して手動で fullGC をトリガーして、ヒープ外のメモリがリサイクルされたかどうかを確認できます。再利用する場合、オフヒープメモリ自体の割り当てが小さすぎる可能性が高く、**-XX:MaxDirectMemorySize** で調整できます。変化がない場合は、jmap を使用して gc できないオブジェクトと、DirectByteBuffer との参照関係を分析します。

GCの問題

良い仕事をしたい場合は、ツールを使用する必要があるため、gc が情報を表示するためにどのツールを使用できるかを知る必要があります。

  • 会社の監視システム: ほとんどの会社は、JVM のさまざまな指標を総合的に監視できるシステムを持っています。
  • JDK の組み込みツールには、jmap や jstat などの一般的なコマンドが含まれています。
    • ヒープメモリの各領域の使用率とGCステータスを表示: jstat -gcutil -h20 pid 1000
    • ヒープ メモリに残っているオブジェクトを表示し、スペースで並べ替えます: jmap -histo pid | head -n20
    • ヒープ メモリ ファイルのダンプ: jmap -dump:format=b,file=heap pid
  • ビジュアル ヒープ メモリ分析ツール: JVisualVM、MAT など。

場合によっては、最初に gc の頻度が高すぎるかどうかを判断し、次のコマンドを使用して gc 生成の変化を観察できます。

jstat -gc pid 1000

ここに画像の説明を挿入

1000 はサンプリング間隔 (ms) を表し、S0C/S1C、S0U/S1U、EC/EU、OC/OU、および MC/MU は、それぞれ 2 つの Survivor 領域、Eden 領域、古い世代、およびメタデータの容量と使用量を表します。エリア。YGC/YGT、FGC/FGCT、GCTは、YGCとFGCの消費時間、時間、総消費時間を表す。gc の方が頻度が高いことがわかった場合は、gc についてさらに分析を行います。

GC ログを使用して問題のトラブルシューティングを行います。起動パラメータに **-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps を追加して、GC ログを有効にします。jdk8 以降の場合は、 -Xlog :gc*** を使用してログを表示します。

GC はプログラムに影響しますか? 深刻度の高いものから低いものまで、以下の4つの状況が含まれると思います。

  • YGC の頻度が高すぎる: YGC がサービスのタイムアウトを引き起こさなくても、YGC の頻度が高すぎるとサービス全体のパフォーマンスが低下します。同時実行性の高いサービスにも注意が必要です。
  • YGC に時間がかかりすぎる: 一般的に言えば、YGC の合計時間は数十または数百ミリ秒になるのが普通です. システムが数ミリ秒または数十ミリ秒フリーズする原因になりますが、この状況はユーザーにはほとんど気付かれません.手順の影響はごくわずかです。ただし、YGC に 1 秒または数秒かかる場合 (FGC にほぼ追いつく場合)、フリーズ時間が長くなり、YGC 自体がより頻繁に発生するため、サービス タイムアウトの問題が発生しやすくなります。
  • FGC の頻度が高すぎる: 通常、FGC は比較的遅く、数百ミリ秒から数秒の範囲です. 通常、FGC は数時間または数日ごとに実行され、システムへの影響は許容されます. ただし、FGC が頻繁に発生すると (たとえば、数十分に 1 回実行される)、ワーカー スレッドが頻繁に停止し、システムが常にスタックしているように見える問題が発生する可能性があります。また、プログラム全体のパフォーマンスが低下します。
  • FGC にかかる時間が長すぎる: FGC にかかる時間が長くなり、それに応じてフリーズ時間も長くなります. 特に同時実行性の高いサービスでは、FGC 中にタイムアウトの問題が発生し、可用性が低下する可能性があります. これもまた注意が必要。

その中でも「FGCが多すぎる」「YGCが長すぎる」という2つの状況は、番組のサービス品質に影響を与える可能性が高い比較的典型的なGCの問題です。残りの 2 つのケースの重大度は低くなりますが、同時実行性や可用性の高いプログラムにも注意が必要です。

YGCの頻度が高すぎる

通常、YGC が頻繁に発生するということは、短期間に小さなオブジェクトが多数存在することを意味します.まず、新しい世代の設定が小さすぎるかどうかを検討し、-Xmn や -XX:SurvivorRatio などのパラメータ設定を調整して問題を解決できるかどうかを確認します。 . パラメータが正常であるにもかかわらず、若い gc の頻度がまだ高すぎる場合は、jmap と mat を使用してダンプ ファイルをさらにチェックする必要があります。

YGC 時間がかかりすぎる

時間がかかりすぎるという問題は、GC ログのどこで時間が費やされているかによって異なります。G1 ログを例にとると、ルート スキャン、オブジェクト コピー、Ref Proc などのステージに焦点を当てることができます。Ref Proc は時間がかかるため、関連オブジェクトの参照には注意が必要です。ルートスキャンは時間がかかるため、スレッド数や世代間参照に注意してください。Object Copy は、オブジェクトのライフサイクルに注意を払う必要があります。さらに、時間のかかる分析には、水平比較が必要であり、これは他のプロジェクトや通常の期間との比較に時間がかかります。たとえば、図の Root Scanning が通常の期間よりも増加する場合は、開始されたスレッドが多すぎることを意味します。

FGCの頻度が高すぎる

FGC が頻繁に発生する原因としては、拡張が必要な​​メモリ不足が原因である可能性があります。または、コードで System.gc() が明示的に呼び出されている場合、ヒープでメモリ リークが発生します。ダンプファイルをさらにチェックするには、jmap と mat を使用する必要があります。

FGC に時間がかかりすぎる

これは対処がより困難であり、各コレクターには異なる処理方法があり、問題を解決するには、独自の特性に応じて構成を調整する必要があります。

ネット障害

ネットワーク レベルに関連する問題は一般的により複雑で、多くのシナリオと困難なポジショニングがあり、ほとんどの開発者にとって悪夢となっており、最も複雑なはずです。ここではいくつかの例を示し、tcp 層、アプリケーション層、およびツールの使用の観点から説明します。

タイムアウト

タイムアウト エラーのほとんどはアプリケーション レベルで発生するため、この記事では概念の理解に重点を置きます。タイムアウトは、接続タイムアウトと読み取り/書き込みタイムアウトに大別できます。接続プールを使用する一部のクライアント フレームワークには、接続タイムアウトとアイドル接続のクリーンアップ タイムアウトもあります。

  • 読み取りと書き込みがタイムアウトしました。readTimeout/writeTimeout、一部のフレームワークは so_timeout または socketTimeout と呼ばれ、どちらもデータの読み取りと書き込みのタイムアウトを指します。ここでのタイムアウトのほとんどは、論理タイムアウトを指していることに注意してください。soa のタイムアウトは、読み取りタイムアウトも参照します。読み取りおよび書き込みのタイムアウトは、通常、クライアントに対してのみ設定されます。
  • 接続がタイムアウトしました。connectionTimeout、クライアントは通常、サーバーとの接続を確立するための最大時間を参照します。サーバー側の connectionTimeout は少し異なります. Jetty はアイドル状態の接続クリーニング時間を示し、tomcat は接続メンテナンスの最大時間を示します.
  • 他の。接続取得タイムアウト connectionAcquireTimeout とアイドル接続クリーンアップ タイムアウト idleConnectionTimeout を含みます。主に、接続プールまたはキューを使用するクライアントまたはサーバー フレームワークに使用されます。

各種タイムアウトを設定する際に確認しなければならないのは、接続が正常に終了するように、クライアント側のタイムアウトをサーバーのタイムアウト時間よりも可能な限り小さく保つことです。
実際の開発で最も気をつけなければならないのは、インターフェイスの読み取りと書き込みがタイムアウトになることです。
適切なインターフェイス タイムアウトを設定する方法が問題です。インターフェースのタイムアウト設定が長すぎると、サーバーの tcp 接続を占有しすぎる可能性があります。また、インターフェイスの設定が短すぎると、インターフェイスが頻繁にタイムアウトになります。
サーバー インターフェイスが明らかに rt を下げているのに、クライアントが常にタイムアウトすることも別の問題です。この問題は、実際には非常に単純です. クライアントからサーバーへのリンクには、ネットワーク送信、キューイング、およびサービス処理が含まれます. 各リンクには時間がかかる場合があります.

TCP キューのオーバーフロー

tcp キュー オーバーフローは比較的低レベルのエラーであり、タイムアウトや rst などのより表面的なエラーを引き起こす可能性があります。したがって、エラーもより微妙なので、個別に説明しましょう。
ここに画像の説明を挿入

上の図に示すように、syns キュー (準接続キュー) と accept キュー (フル接続キュー) の 2 つのキューがあります。サーバーがクライアントのsynを受信した後、メッセージをsynsキューに入れ、syn + ackをクライアントに返信し、サーバーはクライアントのackを受信し、この時点でacceptキューがいっぱいでない場合は取り出しますsyns キューからの一時ストレージ 情報を受け入れキューに入れます。それ以外の場合は、tcp_abort_on_overflow の指示に従います。
tcp_abort_on_overflow 0 は、スリーウェイ ハンドシェイクの 3 番目のステップで受け入れキューがいっぱいになった場合、サーバーがクライアントから送信された ack を破棄することを意味します。tcp_abort_on_overflow 1 は、3 番目のステップで完全な接続キューがいっぱいになった場合、サーバーが最初のパケットをクライアントに送信することを意味し、ハンドシェイク プロセスと接続が廃止されたことを示します。ログをピアリングします
実際の開発では、tcp キューのオーバーフローをすばやく特定するにはどうすればよいでしょうか。次のコマンドを使用します。

netstat コマンドで netstat -s | egrep "listen|LISTEN" を実行

ここに画像の説明を挿入

上の図に示すように、overflowed はフル接続キュー オーバーフローの数を示し、sockets drop は半接続キュー オーバーフローの数を示します。

または次のコマンド:

ss-int

ここに画像の説明を挿入

上記のように、Send-Q は、3 列目のリッスン ポートで完全に接続されたキューの最大数が 5 であることを示し、Recv-Q の 1 列目は、完全に接続されたキューが現在どれだけ使用されているかを示します。
次に、完全に接続されたキューと半接続されたキューのサイズを設定する方法を見てみましょう。
完全に接続されたキューのサイズは、min(backlog, somaxconn) に依存します。バックログはソケットの作成時に渡され、somaxconn は OS レベルのシステム パラメータです。準結合キューのサイズは、最大 (64、/proc/sys/net/ipv4/tcp_max_syn_backlog) によって異なります。
日々の開発ではサーブレットコンテナをサーバーとして利用することが多いため、コンテナの接続キューのサイズに注意を払う必要がある場合があります。バックログは、 tomcat ではacceptCountと呼ばれ、 jetty ではacceptQueueSize と呼ばれます

RST 例外

RST パケットは接続のリセットを意味し、不要な接続を閉じるために使用されますが、通常は異常なシャットダウンを意味し、4 つの手を振るのとは異なります。実際の開発では、 RST パッケージが原因であるピア エラーによる接続のリセット/接続のリセットが
よく見られます。

ポートが存在しません

接続を確立するための SYN 要求が存在しないポートに送信された場合、サーバーは、このポートがないことを検出すると、接続を終了するために RST メッセージを直接返します。

FIN の代わりにアクティブに接続を終了する

一般的に言えば、通常の接続の終了は FIN メッセージで実現する必要がありますが、接続が直接終了することを示す FIN の代わりに RST メッセージを使用することもできます。実際の開発では、SO_LINGER の値を設定して制御することができます.これは、TIMED_WAIT をスキップして対話効率を向上させるために意図的に行われることがよくあります.アイドルでない場合は注意して使用してください.

クライアントまたはサーバーの一方の側で例外が発生し、接続の終了を通知するために他方の側に RST を送信する方向

RST パケットを送信するために前述した tcp キュー オーバーフローは、実際にはこのカテゴリに属します。これは多くの場合、何らかの理由で一方がリクエスト接続を正常に処理できなくなり (たとえば、プログラムがクラッシュした、キューがいっぱいになった)、他方に接続を閉じるように伝えます。

受信した TCP パケットは既知の TCP 接続にありません

たとえば、ネットワークの状態が悪いために一方のマシンが TCP メッセージを失った場合、相手側は接続を閉じ、欠落している TCP メッセージを久しぶりに受信しますが、対応する TCP 接続が存在しないため、直接送信します。新しい接続を開くための RST パケット。

一方が他方からの確認メッセージを長期間受信せず、一定時間または再送信回数後に RST メッセージを送信する

これらの多くはネットワーク環境にも関係しており、ネットワーク環境が悪いと RST パケットが増える可能性があります。
前に、RST パケットが多すぎるとプログラムがエラーを報告すると言いました. クローズされた接続での読み取り操作は接続のリセットを報告し、クローズされた接続での書き込み操作はピアによる接続のリセットを報告します. 通常、パイプライン レベルでのエラーである壊れたパイプ エラーも表示される場合があります。これは、閉じたパイプラインへの読み取りと書き込みが、多くの場合、RST を受信して​​接続リセット エラーを報告した後、データグラムの読み取りと書き込みを続行するエラーであることを示しています。 glibc のソース コードのコメントでも紹介されています。
トラブルシューティング時に RST パケットの存在を確認するにはどうすればよいですか? もちろん、tcpdump コマンドを使用してパケットをキャプチャし、wireshark を使用して簡単な分析を行います。tcpdump -i en0 tcp -w xxx.cap, en0 は監視ネットワーク カードを示します。
ここに画像の説明を挿入

次に、wireshark を介してキャプチャされたパケットを開くと、次の図が表示されます。赤いものは RST パケットを表します。
ここに画像の説明を挿入

TIME_WAITとCLOSE_WAIT

TIME_WAIT と CLOSE_WAIT の意味は誰もが知っていると思います。オンラインの場合、コマンドを直接使用できます

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

time-wait と close_wait の数を表示するには、
ss コマンドを使用する方が高速です。

ss -アリ | awk '{++S[$1]} END {for(a in S) print a, S[a]}'

TIME_WAIT

time_wait の存在は、失われたデータ パケットが後続の接続によって多重化されるためであり、2 つ目は、2MSL の時間範囲内で正常に接続を閉じるためです。その存在により、実際には RST パケットの発生が大幅に減少します。
過度の time_wait は、短い接続が頻繁に発生するシナリオで発生する可能性が高くなります。この場合、サーバー側でいくつかのカーネル パラメータの調整を行うことができます。

#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1

もちろん、NAT 環境ではタイム スタンプが間違っているためにデータ パケットが拒否されることを忘れてはなりません.別の方法として、tcp_max_tw_buckets を減らすこともできます。バケット テーブルのオーバーフローが報告されるのを待ちます。間違っています。

CLOSE_WAIT

close_wait は、多くの場合、アプリケーション プログラムに問題があり、ACK の後に再度 FIN メッセージが送信されないことが原因です。close_wait の確率は time_wait の確率よりもさらに高く、結果はより深刻です。多くの場合、特定の場所がブロックされ、接続が正常に閉じられず、徐々にすべてのスレッドが消費されます。
この種の問題を見つけたい場合は、jstack を使用してスレッド スタックを分析し、問題を解決するのが最善の方法です. 詳細については、上記の章を参照してください. これはほんの一例です。
開発者は、アプリケーションがオンラインになってからハングアップするまで CLOSE_WAIT が増加し続けたと言いました. jstack の後、ほとんどのスレッドが countdownlatch.await メソッドでスタックしていたので、疑わしいスタックを見つけました. threading は使用されましたが、それはありませんでした. catch 例外、変更後、例外は、sdk のアップグレード後に頻繁に発生する、見つかっていない最も単純なクラスのみであることがわかりました。

おすすめ

転載: blog.csdn.net/h295928126/article/details/126941638