blog

メモリリークを解決する(1)-ApacheKylin内部 ThreadLocalMapリーク問題分析

オープンソース製品は日進月歩ですが、落とし穴もありがちです。時には予期せぬ問題が発生し、解決するためにコードを研究する必要があります。メモリリークは非常によくある問題で、サービスが不安定になったり、可...

Oct 11, 2020 · 4 min. read
シェア

オープンソースの製品は繰り返しが早い反面、落とし穴もありがちです。時には予期せぬ問題が発生し、それを解決するためにコードを研究する必要があります。メモリリークは、サービスが不安定になり、可用性に影響する非常に一般的な問題です。この記事では、MAT と BTrace を使用して apache kylin のメモリリークを解決する方法について、問題の特定、原因の分析、推測の検証方法を明確にすることに重点を置いて説明します。

同じようなメモリー・リークに遭遇したときに、そこから学ぶことができるように。

背景

会社のセルフサービスレポーティングビジネスをKylin 2.0クラスタからKylin 3.0クラスタに移行したとき、Kylinのジョブロールのすべてのプロセスが2〜3日ごとにOOMになり、サービスが非常に不安定で、早急に解決する必要がありました。

調査アイデア

ビルドサービスは32GBのヒープメモリを持つJavaプロセスであり、OOMは本当のメモリ不足かメモリリークです。レポートサービスで使用されているkylin 2.0も32GBのメモリを搭載しており、同様のOOMが発生しなかったことを考慮すると、メモリリークは2.0以降の新機能によって導入された問題である可能性がまず疑われます。

kylin 3.0のスモールクラスタがOOMなしで長期間使用されていることを改めて考えると、業務量と使用状況に関係があるのではないかと思います。レポーティング業務は、異なるビルドモデルで1日に何千回もビルドされるため、問題が露呈しやすいのです。一般的に、コンテナ、Netty、ThreadLocalがメモリ・リークの影響を最も強く受けます。メモリ・リークを調査するには、メモリ解析ツールMATを使ってヒープ・メモリを解析します。どのオブジェクトが大量のメモリーを使用しているか、オブジェクト参照も含めて調べます。疑わしいオブジェクトを見つけたら、さらにコード解析を行い、BTraceを使ってコールログを出力して問題の原因を特定し、最終的に問題を解決します。

ポジショニングの問題

MAT(メモリーアナライザーツール)を使ってhprofファイルを解析することができます。ヒープメモリが数十ギガバイトあり、解析には数十ギガバイトの空きメモリが必要であることに注意してください。一般に、MATは空きメモリのあるテストサーバ上に配置され、vncserverは視覚的な操作サポートを提供するために使用されます。

hprofファイルをMATサーバーにコピーし、MATを起動してhprofファイルをロードします。

ロード時にエラーが発生した場合

An internal error occurred during: 
"Parsing heap dump from **\java_pid6564.hprof'".Java heap space

少なくともhprofファイルよりもMATの起動メモリを増やす必要があります。

open the MemoryAnalyzer.ini file
change the default -Xmx1024m to a larger size

MATの分析結果

上の図から、より多くのメモリを消費するオブジェクトは、スケジューラスレッドや他のスレッドのスレッドオブジェクトであり、それぞれがより多くのメモリを消費していることがわかります。各ダンプについて、スレッドオブジェクトのサイズは200MBから1GBまで様々です。しかし、同じダンプでは、各スレッドオブジェクトのサイズはほとんど同じです。スレッドは主にInternalThreadLocalMapオブジェクトがメモリを占有し、メンバー変数Object[]配列の長さは数千万から数億にもなります。十中八九、これはメモリリークです。

では、InternalThreadLocalMapとはどのようなオブジェクトなのでしょうか?

理由を分析

gitのblameで、InternalThreadLocalMapがKYLIN-3716で導入されていることがわかりました。jiraを見ると、nettyの関連コードから拝借したもののようで、ThreadLocalへの参照がInternalThreadLocalに置き換えられ、内部で読み込まれたコンテキストへのクエリ要求が高速化されるようです。では、高速化の原理は?

ThreadLocal は、マップを使用してスレッド内の各ローカル オブジェクトへの参照を保持します。

そして、InternalThreadLocalはスレッド内部のローカル・オブジェクト配列を保持し、それらを使用するときに配列を検索します。javaの場合、マップよりも配列のインデックスでオブジェクトを探す方が高速です。実装の観点からは、InternalThreadLocalオブジェクトをビルドするたびに、有効なインデックスビットのOjbect[]に1が追加され、ローカルスレッド内で対応するオブジェクトの参照をキャッシュします。InternalThreadLocalオブジェクトは、特に大きな数を構築する場合しかし、インデックスビットはマイナスではなく、唯一のプラスは、オブジェクト[]の長さが非常に大きくなり、メモリの問題が発生します。

一般的にThreadLocalオブジェクトは静的なメンバ変数として使用され、数十個のオブジェクトを持つプロセスは多く、InternalThreadLocalに置き換えれば、配列はそれほど大きくならず、問題はないでしょう。しかし、チェックを外したまま、通常のメンバ変数として使用すると、メモリリークの危険性があります。

kylinプロジェクトの参照を調べた結果、ほとんどの参照は静的メンバ変数への参照ですが、DataTypeSerializerのようにメンバ変数にInternalThreadLocalも使用しているクラスがいくつかあり、メモリリークを引き起こす可能性があることがわかりました。理論的には、オブジェクト変数への参照を ThreadLocal に戻せば問題は解決しますが、この推測が正しいかどうかを検証するのが賢明でしょう。

推測を検証

BTraceスクリプトは、インターセプトコードを追加することができますメソッドコールのコンテキストを表示します。JVMにBTraceスクリプトは、コードの行を変更する必要はありません、再起動する必要はありません、あなたが望むログを出力することができ、魔法の武器の問題の行を見つけることです。あなたは私にメッセージを残す必要がある場合は、独自のGoogleを使用する方法。

BTrace スクリプトのログを見ると、ビルド・サービスが InternalThreadLocal オブジェクトを頻繁にビルドしていることがわかります。InternalThreadLocalのビルド数は数時間で数千万に達します。一方、クエリーサービスには頻繁にビルドされる問題はなく、推測が正しいことが検証されています。

最適化結果

オブジェクトのメンバ変数のInternalThreadLocalリファレンスをThreadLocalに戻し、サービスを再起動します。InternalThreadLocalオブジェクトは、わずか十数回のビルドで数日間実行され、時間の経過とともに増加することはなく、OOMもなくなり、問題は解決しました。関連prはkylinコミュニティに投稿されています。

Read next

奇妙なクロスドメイン問題の足踏み日記

この問題が発生した場合、まず考えられるのは問題のCORS設定ですが、OP先生がサーバーのCORSルール設定を確認したところ、クロスドメインが許可されていました。構成の一部は次のとおりです:だから... 正確にはどこに問題がありますか? キャンバスの処理クロスドメインの画像で、このロジックは、対処するためのより一般的な方法であり、また、画像リソースのアドレスを切り替えるには、関数は通常の使用です。 もし上記のロジック...

Oct 11, 2020 · 1 min read