web-dev-qa-db-ja.com

大きなメモリリークを見つける方法Javaヒープダンプ

Javaアプリケーションでメモリリークを検出する必要があります。これについてある程度の経験はありますが、このための方法論/戦略に関するアドバイスが必要です。参照やアドバイスは大歓迎です。

私たちの状況について:

  1. ヒープダンプが1 GBより大きい
  2. 5回のヒープダンプがあります。
  3. これを引き起こすテストケースはありません。これは、少なくとも1週間使用した後の(大規模な)システムテスト環境でのみ発生します。
  4. このシステムは、内部で開発されたレガシーフレームワークに基づいて構築されているため、設計上の欠陥が多く、すべてを数えることは不可能です。
  5. 誰もフレームワークを深く理解していません。それは、インドのone男に転送されました。
  6. 時間の経過とともにスナップショットヒープダンプを実行し、時間の経過とともに増加する単一のコンポーネントはないと結論付けました。ゆっくりと成長するすべてのものです。
  7. 上記は、制限なしでその使用を増加させるのはフレームワーク自作のORMシステムであるという方向性を示しています。 (このシステムはオブジェクトをファイルにマップしますか?!したがって実際にはORMではありません)

質問:エンタープライズ規模のアプリケーションでリークを特定するのに役立つ方法は何ですか?

29

基礎となるコードをある程度理解していなければ、ほとんど不可能です。基礎となるコードを理解していれば、ヒープダンプで取得している膨大な数の情報から、小麦をより適切に分類できます。

また、クラスがなぜ最初にそこにあるのかがわからなければ、何かがリークであるかどうかを知ることはできません。

私はちょうどこれを行うために過去2週間を費やしたばかりで、反復プロセスを使用しました。

まず、ヒーププロファイラーは基本的に役に立たないことがわかりました。巨大なヒープを効率的に分析することはできません。

むしろ、私は jmap ヒストグラムのみにほぼ依存していました。

あなたはこれらに精通していると思いますが、そうでない人のために:

jmap -histo:live <pid> > dump.out

ライブヒープのヒストグラムを作成します。簡単に言うと、クラス名と、ヒープ内にある各クラスのインスタンスの数がわかります。

私は定期的に、1日24時間、5分おきにヒープをダンプしていました。細かすぎるかもしれませんが、要点は同じです。

このデータに対していくつかの異なる分析を実行しました。

2つのヒストグラムを取得し、それらの違いをダンプするスクリプトを作成しました。したがって、Java.lang.Stringが最初のダンプで10、2番目のダンプで15だった場合、スクリプトは「5 Java.lang.String」を吐き出して、5上がったことを通知します。数は負になります。

次に、これらの違いのいくつかを取り、実行ごとに下がったすべてのクラスを取り除き、結果を結合します。最後に、特定の期間にわたって継続的に成長しているクラスのリストがあります。明らかに、これらはリークするクラスの主要な候補です。

ただし、一部のクラスは保存されているクラスもあれば、GCされているクラスもあります。これらのクラスは全体的に簡単に上下できますが、それでもリークします。したがって、「常に上昇している」クラスのカテゴリから外れる可能性があります。

これらを見つけるために、データを時系列に変換し、データベース、特にPostgresにロードしました。 Postgresは 統計集計関数 を提供するので便利です。したがって、データに対して単純な 線形回帰分析 を実行し、常にそうでなくても、傾向が上がるクラスを見つけることができます。チャートの上に。正の勾配を持つクラスを探すために、regr_slope関数を使用しました。

このプロセスは非常に成功し、本当に効率的であることがわかりました。ヒストグラムファイルはそれほど大きくなく、ホストから簡単にダウンロードできます。これらは、運用システムで実行するのにそれほど高価ではありませんでした(それらは大きなGCを強制し、VMが少しの間ブロックする可能性があります)。2Gのシステムでこれを実行していました= Javaヒープ。

これでできることは、リークの可能性があるクラスを特定することだけです。

これは、クラスがどのように使用されているか、そしてそれらがプレイすべきかどうかを理解するための場所です。

たとえば、Map.Entryクラスやその他のシステムクラスがたくさんある場合があります。

単にストリングをキャッシュしているのでない限り、これらのシステムクラスは、おそらく「違反者」ではありますが、「問題」ではないのが実情です。いくつかのアプリケーションクラスをキャッシュしている場合、そのクラスは問題がどこにあるかを示すより良い指標です。 com.app.yourbeanをキャッシュしない場合、関連付けられたMap.Entryは関連付けられません。

いくつかのクラスを取得したら、インスタンスと参照を探してコードベースのクロールを開始できます。自分のORMレイヤー(善悪を問わず)があるので、少なくともそのソースコードを簡単に見ることができます。 ORMがキャッシュしている場合、アプリケーションクラスをラップするORMクラスをキャッシュしている可能性があります。

最後に、もう1つの方法は、クラスがわかったら、サーバーのローカルインスタンスを起動し、ヒープとデータセットをはるかに小さくして、プロファイラーの1つを使用することです。

この場合、リークしていると思われるものの1つ(または少数)にのみ影響する単体テストを実行できます。たとえば、サーバーを起動してヒストグラムを実行し、単一のアクションを実行して、ヒストグラムを再度実行できます。リークしているクラスは1(または作業単位が何であれ)増加しているはずです。

プロファイラーは、「現在リークされている」クラスの所有者を追跡するのに役立つ場合があります。

ただし、最終的には、リークとは何か、そうでないもの、およびオブジェクトがヒープ内に存在する理由を理解するために、コードベースをある程度理解する必要があります。ヒープのリークとして。

59
Will Hartung

Eclipse Memory Analyzer を見てください。これは、1)非常に大きなヒープを非常に高速に開くことができ、2)非常に優れた自動検出ツールがいくつかある優れたツールです(自己完結型、Eclipse自体をインストールする必要はありません)。後者は完璧ではありませんが、EMAには、ダンプ内のオブジェクトをナビゲートしてクエリを実行し、リークの可能性を見つけるための非常に優れた方法が数多く用意されています。

私は過去にこれを使用して、不審なリークを追跡しました。

13
matt b

この回答は、@ Will-Hartungのものを拡張したものです。同じプロセスを適用して私のメモリリークの1つを診断し、詳細を共有すると他の人の時間を節約できると考えました。

アイデアは、各クラスのpostgresの「プロット」時間とメモリ使用量を比較し、成長を要約する線を引き、最も速く成長しているオブジェクトを特定することです。

    ^
    |
s   |  Legend:
i   |  *  - data point
z   |  -- - trend
e   |
(   |
b   |                 *
y   |                     --
t   |                  --
e   |             * --    *
s   |           --
)   |       *--      *
    |     --    *
    |  -- *
   --------------------------------------->
                      time

ヒープダンプ(複数必要)を、ヒープダンプ形式からpostgresで使用するのに便利な形式に変換します。

 num     #instances         #bytes  class name 
----------------------------------------------
   1:       4632416      392305928  [C
   2:       6509258      208296256  Java.util.HashMap$Node
   3:       4615599      110774376  Java.lang.String
   5:         16856       68812488  [B
   6:        278914       67329632  [Ljava.util.HashMap$Node;
   7:       1297968       62302464  
...

各ヒープダンプの日時を含むcsvファイルへ:

2016.09.20 17:33:40,[C,4632416,392305928
2016.09.20 17:33:40,Java.util.HashMap$Node,6509258,208296256
2016.09.20 17:33:40,Java.lang.String,4615599,110774376
2016.09.20 17:33:40,[B,16856,68812488
...

このスクリプトを使用する:

# Example invocation: convert.heap.hist.to.csv.pl -f heap.2016.09.20.17.33.40.txt -dt "2016.09.20 17:33:40"  >> heap.csv 

 my $file;
 my $dt;
 GetOptions (
     "f=s" => \$file,
     "dt=s" => \$dt
 ) or usage("Error in command line arguments");
 open my $fh, '<', $file or die $!;

my $last=0;
my $lastRotation=0;
 while(not eof($fh)) {
     my $line = <$fh>;
     $line =~ s/\R//g; #remove newlines
     #    1:       4442084      369475664  [C
     my ($instances,$size,$class) = ($line =~ /^\s*\d+:\s+(\d+)\s+(\d+)\s+([\$\[\w\.]+)\s*$/) ;
     if($instances) {
         print "$dt,$class,$instances,$size\n";
     }
 }

 close($fh);

データを入れるテーブルを作成します

CREATE TABLE heap_histogram (
    histwhen timestamp without time zone NOT NULL,
    class character varying NOT NULL,
    instances integer NOT NULL,
    bytes integer NOT NULL
);

データを新しいテーブルにコピーします

\COPY heap_histogram FROM 'heap.csv'  WITH DELIMITER ',' CSV ;

サイズ(バイト数)クエリに対してslopクエリを実行します。

SELECT class, REGR_SLOPE(bytes,extract(Epoch from histwhen)) as slope
    FROM public.heap_histogram
    GROUP BY class
    HAVING REGR_SLOPE(bytes,extract(Epoch from histwhen)) > 0
    ORDER BY slope DESC
    ;

結果を解釈します。

         class             |        slope         
---------------------------+----------------------
 Java.util.ArrayList       |     71.7993806279174
 Java.util.HashMap         |     49.0324576155785
 Java.lang.String          |     31.7770770326123
 joe.schmoe.BusinessObject |     23.2036817108056
 Java.lang.ThreadLocal     |     20.9013528767851

勾配は、1秒あたりに追加されるバイト数です(エポックの単位は秒単位であるため)。サイズの代わりにインスタンスを使用する場合、それは1秒あたりに追加されるインスタンスの数です。

このjoe.schmoe.BusinessObjectを作成するコード行の1つがメモリリークの原因でした。オブジェクトを作成し、既に存在するかどうかを確認せずに配列に追加していました。他のオブジェクトも、リークしているコードの近くにBusinessObjectとともに作成されました。

7
joseph

時間を短縮できますか?つまり、数分または数時間で1週間分の呼び出し/リクエストなどを強制するダミーテストクライアントを作成できますか?これらはあなたの最大の友達であり、あなたが持っていない場合は、書いてください。

先ほど、Netbeansを使用してヒープダ​​ンプを分析しました。少し遅いかもしれませんが、効果的でした。 Eclipseがクラッシュし、32ビットWindowsツールもクラッシュしました。

64ビットシステムまたは3 GB以上のLinuxシステムにアクセスできる場合は、ヒープダンプを分析する方が簡単です。

変更ログとインシデントレポートにアクセスできますか?大規模企業には通常、変更管理チームとインシデント管理チームがあり、問題が発生し始めた時期を追跡するのに役立ちます。

いつうまくいかなくなったのですか?人と話をして、いくつかの歴史を試してみてください。 「うん、奇妙なことが起こったのはパッチ6.43でXYZを修正した後だった」と誰かが言うかもしれません。

3
Fortyrunner

IBM Heap Analyzer で成功しました。オブジェクトサイズの最大の減少、最も頻繁に発生するオブジェクト、サイズでソートされたオブジェクトなど、ヒープのいくつかのビューを提供します。

2
Drew Johnson

それが1週間の使用後に発生し、アプリケーションがあなたが説明したようなビザンチンである場合、おそらく毎週それを再起動した方がよいでしょうか?

私はそれが問題を修正していないことを知っていますが、それは時間効率の良いソリューションかもしれません。停止が発生する可能性のある時間枠はありますか? 2番目のインスタンスを維持したまま、1つのインスタンスをロードバランスしてフェイルオーバーできますか?おそらく、メモリ使用量が特定の制限に違反したときに再起動をトリガーできます(おそらくJMXなどで監視しています)。

1
Brian Agnew

ヒープダンプを分析するためのEclipse MATやHeap Heroなどの優れたツールがあります。ただし、これらのツールには、正しい形式および正しい時点でキャプチャされたヒープダンプを提供する必要があります。

この記事では、ヒープダンプをキャプチャするための複数のオプションについて説明します。ただし、私の意見では、最初の3つは効果的な使用方法であり、他の方法は注意することをお勧めします。 1. jmap 2. HeapDumpOnOutOfMemoryError 3. jcmd 4. JVisualVM 5. JMX 6.プログラムによるアプローチ7. IBM管理コンソール

7キャプチャするオプションJavaヒープダンプ

1
Jim T

私は jhat を使用しましたが、これは少し厳しいですが、使用しているフレームワークの種類によって異なります。

0
LB40