プロセス情報を使用してメモリ リークを追跡する
要約: メモリ リークは、バックグラウンド サーバー プログラムで発生する一般的なソフトウェアの問題です。valgrind など、メモリ リークを特定する方法は数多くありますが、プロセスを再起動する必要があります。場合によっては、プロセスを再起動した後に同じメモリ リークを再現することが困難であるか、時間がかかることがあります。この記事では、メモリ リークが発生した既存のプロセス インスタンスを使用して分析し、メモリ リーク ポイントの取得を試みる方法を検討します。
1. 問題現象
Bigpipe は、Baidu の内部分散伝送システムであり、そのサーバー モジュール Broker は非同期プログラミング フレームワークを使用して実装されており、オブジェクト リソースのライフ サイクルとリリース タイミングを管理するために広範な参照カウントを使用しています。 Broker モジュールのストレス テスト中に、Broker を長時間実行するとメモリ使用量が徐々に増加し、メモリ リークが発生することが判明しました。
2. 予備分析
Broker の最近のアップグレードに基づいて、Broker でメモリ リークが発生している可能性のあるオブジェクトを特定します。ブローカーは新しい監視機能を追加しました。その 1 つは、サーバーのさまざまなパラメータの監視統計です。これには、パラメータ オブジェクトに対する読み取り操作が必要であり、各操作では参照カウントが「1 増加」し、その後「1 減少」します。操作が完了しました。現在、複数のパラメータ オブジェクトが存在しており、どのパラメータ オブジェクトが漏洩しているかを判断する必要があります。
3. コードとビジネスの分析
1. 前の予備分析の結果を証明するには、次のような方法が考えられます: Valgrind を使用してブローカーを実行し、ストレス プログラムを開始してメモリ リークの可能性を再現します。ただし、この方法を使用すると、
1) メモリ リークの発生条件は単純ではないため、再発期間が長くなり、同じメモリ リークを再現することさえできません
2) メモリ リークのオブジェクトが配置されます。コンテナ内では、valgrind は通常の終了後に関連するメモリ リークを報告しません
再現を試みるために別のテスト クラスターで短期間実行した後、Valgrind は例外を報告しませんでした。
2. 既存の状況を分析します。幸いなことに、「メモリ リーク」問題のある Broker プロセスはまだ実行中であり、真実はこのプロセス内にあります。問題を特定するには、既存のシーンを最大限に活用する必要があります。最初はデバッグに GDB を使用したいと考えています。
3. 課題: GDB の Attach pid メソッドを使用すると、プロセスがハングします。別のマスター/スレーブ ブローカーがペアになり、相互にハートビートを送信しないと、ブローカーは自動的にプログラムを終了します。終了後はシーンを保存できなくなります。つまり、GDB を使用できる機会は 1 回だけです。
4. 解決策: gdb を使用してメモリ情報を出力し、その情報からメモリ リークの可能性のあるポイントを観察します。
5. ステップ 1: pmap -x {PID} でメモリ情報を表示します (例: pmap -x 24671)。anon とマークされた場所に注目してください:
SHAPE * MERGEFORMAT
24671 : ./bin/broker アドレス Kbytes RSS Anon ロック モード マッピング 0000000000400000 11508 - - - r-x-- ブローカー 000000000103c000 - - - rw--- ブローカー 00000 0000109d000 144508 - - - rw--- [ anon ] 00007fb3f583b000 4 - - - rw--- libgcc_s-3.4.5-20051201.so.1 ------------- ----- - -- ---- ------ ------ 合計 KB 610180 - - -
|
6. ステップ 2: gdb ./bin/broker を起動し、attach {PID} コマンドを使用して既存のプロセスをロードします。たとえば、上記のプロセス番号は 24671 です。ステップ 24671 を使用します。 3: setheight 0 と
setlogging on を使用して gdb ログを有効にすると、ログは gdb.txt ファイルに保存されます。 ステップ 4: x/{メモリ バイト数}a {メモリ アドレス} を使用します。メモリ情報の一部を出力するには、たとえば、上記の anon はヒープ ヘッダー アドレスであり、144508kb のメモリを占有するため、次を使用します。 x/18497024a0x000000000109d000; コマンド ラインが多数ある場合は、周辺のコマンド ラインを編集できます。それを gdb コマンド ライン プロンプトに直接ポストして実行するか、コマンド ラインを変更して command.txt などのテキスト ファイルに書き込み、gdb コマンド ライン プロンプトで sourcecommand.txt を使用してコマンド セットを実行します。以下は command.txt ファイルの内容です。
SHAPE * MERGEFORMAT set height 0
set logon
x/18497024a 0x000000000109d000 x/ 23552a 0x000000317ae09000
x/2048 a 0x000000317b65e000
x/512a 0x000000318a821000
x/2560a 0x000000318b18d000
9 。 5: gdb.txt ファイルの情報を分析します。 gdb.txt の内容は次のとおりです。 SHAPE *マージフォーマット |
0x1071000 <_ZN7bigpipe13bmq_handler_t16_heart_beat_bodyE+832>: 0x0 0 x0 0x1071010 <_ZN7bigpipe13bmq_handler_t16 _heart_beat_bodyE+848> : 0x0 0x0
…
0x10710c0 <_zgvz5getippce4lock>: 0x0 0x0
0x10710d0 <_zgvzn7bigpipe13bmq_handler_t 14get_heart_beaterie4__sl>: 0x0 0x0 0x10710e0 <_zst8__ioinit>: 0x0 0x0 0x10710f0 <_zgvz5getippce4lock>: 0x0 0x0 … 0x22c2f 00: 0x10200d0 <_ZTVN7bigpipe14BigpipeDIEngineE+16> 0x4600000001 0x22c2f10: 0x1 0x117087b 0x22c2f20: 0x0 0 x1214495 … 0x22c2f70: 0x0 0x0 0x22c2f80: 0x0 0x0 0x22c2f90: 0x0 0x0 … Gdb .txの内容の説明と分析t : 最初の列は現在のメモリアドレスです (例: ) 0x22c2f00 ; 2 番目、3 番目、および 4 番目の列は、現在のメモリ アドレス (16 進数で表現) に対応して格納されている値であり、gdb のデバッグ シンボル情報、例: 0x10200d0<_ZTVN7bigpipe15BigpipeDIEngineE+16>0x4600000001 、それぞれ、「最初の 16 バイト」、「シンボル情報 (+16 のオフセットに注意)」、「最後の 16 バイト」を意味します。ただし、すべてのアドレスで gdb のデバッグ シンボル情報が出力されるわけではなく、場合によっては 3 番目にシンボル情報が表示されることがあります。列に表示され、2 番目の列に表示される場合もあります。メモリ アドレス 0x22c2f00 の上記の行には、bigpipe::BigpipeDiEngine クラスによって生成されたオブジェクトの 1 つの仮想デストラクターの関数ポインター、つまり 仮想関数テーブル ポインター (vptr) が格納されます。ここで、アドレス 0x10200d0 は近くのメモリに保存されます。以下に示すように、BigpipeDiEngine クラスの 仮想関数テーブル (vtbl) である必要があります。 SHAPE * マージフォーマット (gdb) db) x/i 0x53e2c6 0x53e2c6 : プッシュ %rbp (gdb) x /a 0x53e2c6 0x53e2 c6 : 0xec834853e5894855 アドレス 0x10200d0の値これは、BigpipeDiEngine クラスのデストラクターを指すアドレス、つまり実際のデストラクター コード セグメント ヘッダー アドレス 0x53e2c6 です。上記の実行結果から分かるように、アドレス0x53e2c6の「シンボル情報」はデストラクタ名であり、そのアセンブリコマンドはpushである。したがって、最初に表示される0x22c2f00のアドレスがオブジェクトの仮想デストラクタポインタであることが分かり、表示されている「シンボル情報」BigpipeDIEngineを元にこのクラス(仮想デストラクタ付き)を決定することができます。クラス)生成されたインスタンスの数を確認し、排出されたインスタンスの数に基づいてさらに判断します。 そこで、gdb.txtをソートし、適当な処理をしてシンボル(クラス名/関数名)の出現回数の一覧を取得します。たとえば、山括弧で「シンボル情報」部分から上記の内容をフィルターし、出現数で並べ替えるには、次のようなコマンドを使用できます。 catgdb.txt |grep "<"|awk -F '<' '{print $2} ' |awk -F '>''{print $1}' |sort |uniq -c|sort -rn > プロジェクト関連の変数を除外しますプレフィックス (bmq、Bigpipe、bmeta など) cat result.txt|grep -P"bmq|Bigpipe|bigpipe|bmeta"|grep "_ZTV" > result2.txt ; 次のようなリストを取得します。 :
| SHAPE * マージフォーマット
35782 _ZTVN7bigpipe14CConnectE+16
282 _ZTVN3bsl3var4IVarE+16
179 _ZTVN7bigpipe19bmeta_ストライプ_info_tE+16 26 _ZTV13AutoKylinLockI5MutexE+ 16 21 _ZTVN6google8protobuf8internal26GeneratedMessageReflectionE+16 _ZTVN6comcfg17制約ライブラリ12ラップ関数E+16 8 _ZTVN3bsl3var11BasicStringINS_12basic_stringIcNS_14 pool_allocatorIcEEEEEE+16 6 _ZTVN7bigpipe19bmeta_broker_info_tE+16 6 _ZTVN7bigpipe15BigpipeDIEngineE +16
10. 次に、このプロジェクトに関連し、最も頻繁に表示されるオブジェクトを見つけます。この問題により、CConnect オブジェクトが 1 ずつ減分され、正常に解放されなくなります。
| 11. 新しい「監視」機能とCConnectに関するコードを辿ると以下のようになります。
SHAPE * MERGEFORMAT
if (atomic_add (&_count, -1) == 0) {
_free(_conn)
}
4. 真実が明らかになります
atomic_add 関数の実装 (以下に示すように) を見ると、戻り値は自己インクリメント (デクリメント) 前の値であることがわかります。特にそのような意味を表現していないため、この関数の呼び出しを作成者がインクリメント後の値であると誤って使用し、最終的に参照カウントが 0 ではないと誤って認識したため、_free 操作が実行されず、結果的にメモリーリーク。通常、__sync_fetch_and_add に対応する関数も __sync_add _and_fetch です。この 2 つの違いは、「値を先に取得してから取得する」か「値を加算してから取得する」かです。
SHAPE * MERGEFORMAT
atomic_add(volatile int *count, int add)
{
register int __res;
__res = __sync_fetch_and_add(count, add);レス;
}
| 5. 解決策 したがって、プログラムは次のように改善されます:
SHAPE * MERGEFORMAT
if (atomic_add_and_fetch (&_count, -1) == 0) {
_free(_conn) }
|
6. まとめ 1. 非同期フレームワークによって実装されたプログラムは問題を見つけて追跡することがより難しいため、問題の再現を完了するにはログ、gdb、pmap などの手段を統合する必要があります。
2. Valgrind はメモリ リークを検出する唯一の方法ではなく、特定の制限があります
3. 関数の関数を示すために、関数名の定義は、一部のエラーを回避できるようにする必要があります。
4. ライブラリ関数のドキュメントをよく読み、その使用方法を理解する必要があります。 1) gdb を使用してメモリ情報を出力する場合は、状況に準拠する必要があります。ここで、インスタンスの数とメモリ情報のシンボルは 1 対 1 の関係にあります。上記の実践では、CConnect クラスは仮想デストラクタを持っているため、仮想関数テーブル ポインタはメモリ情報に表示されます。表示されるシンボルと 1 対 1 に対応するため、この種の推測条件でメモリ リークとして使用できます。リークされたメモリが残らない場合、トレースはそのシンボルに関する有効な情報を取得できません。メモリ リーク; 2) メモリ リークをオフラインで再現しようとして失敗したが、メモリ リークのあるプロセス (サイト) がまだオンラインに存在する場合は、上記の方法を使用して既存のプロセス (サイト) からメモリ リークを削除してみることができます。 ) より多くのメモリ リーク情報を取得するため。 3) この方法は、メモリ リークを発生させた既存のプロセス (オンサイト) を分析に使用でき、既存の問題プロセスを最大限に活用できます。メモリリークのデバッグ方法 参考になる補足、試してみる価値のある方法です。
Baidu MTC は業界をリードするモバイル アプリケーション テスト サービス プラットフォームで、モバイル アプリケーション テストで開発者が直面するコスト、テクノロジー、効率の問題に対するソリューションを提供します。同時に、業界をリードする Baidu テクノロジーが共有され、執筆者は Baidu の従業員と業界のリーダーから構成されています。
>>ご質問がございましたら、お気軽にお問い合わせください
http://www.bkjia.com/PHPjc/1090824.html
www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1090824.html技術記事プロセス情報を使用してメモリ リークを追跡する方法の概要: メモリ リークは、バックグラウンド サーバー プログラムで発生する一般的なソフトウェアの問題です。valgrind など、メモリ リークを特定する方法はたくさんありますが、再起動が必要です...。
|
|
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
-
2024-10-22 09:46:29
-
2024-10-13 13:53:41
-
2024-10-12 12:15:51
-
2024-10-11 22:47:31
-
2024-10-11 19:36:51
-
2024-10-11 15:50:41
-
2024-10-11 15:07:41
-
2024-10-11 14:21:21
-
2024-10-11 12:59:11
-
2024-10-11 12:17:31