35

Javaアプリケーションでメモリリークを見つける必要があります。私はこれについてある程度の経験がありますが、このための方法論/戦略についてアドバイスを求めています。どんな参考やアドバイスも歓迎します。

私たちの状況について:

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

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

4

8 に答える 8

65

基礎となるコードをある程度理解していなければ、ほとんど不可能です。基礎となるコードを理解していれば、ヒープ ダンプで取得している無数のビットの情報をより適切に選別することができます。

また、そもそもクラスが存在する理由を知らなければ、何かがリークしているかどうかを知ることはできません。

私はまさにこれを行うのに過去数週間を費やし、反復プロセスを使用しました。

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

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

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

jmap -histo:live <pid> > histogram.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 クラスやその他のシステム クラスがあることに気付く場合があります。

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

いくつかのクラスを作成したら、インスタンスと参照を探してコード ベースのクロールを開始できます。独自の ORM レイヤーがあるため (良くも悪くも)、少なくともそのソース コードを簡単に確認できます。ORM で何かをキャッシュしている場合は、アプリケーション クラスをラップする ORM クラスをキャッシュしている可能性があります。

最後に、もう 1 つできることは、クラスを理解したら、はるかに小さいヒープと小さいデータセットでサーバーのローカル インスタンスを起動し、それに対してプロファイラーの 1 つを使用することです。

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

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

しかし、最終的には、何がリークで何がリークでないか、オブジェクトがヒープに存在する理由、ましてや保持されている理由をよりよく理解するために、コード ベースをある程度理解する必要があります。あなたのヒープのリークとして。

于 2010-03-24T21:56:40.170 に答える
13

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

過去に、疑わしいリークを追跡するために使用しました。

于 2010-03-24T20:58:57.693 に答える
8

この回答は、@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 ;

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

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 とともに作成されました。

于 2016-10-28T16:01:16.230 に答える
3

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

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

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

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

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

于 2010-03-24T21:08:27.790 に答える
2

私は IBM Heap Analyzerで成功しました。オブジェクト サイズの最大のドロップオフ、最も頻繁に発生するオブジェクト、サイズで並べ替えられたオブジェクトなど、ヒープのいくつかのビューを提供します。

于 2010-03-24T21:39:29.717 に答える
1

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

問題が解決しないことはわかっていますが、時間効率の良い解決策になる可能性があります。停止できる時間枠はありますか? 2 つ目のインスタンスを維持しながら、1 つのインスタンスの負荷を分散してフェールオーバーできますか? おそらく、メモリ消費が特定の制限を超えたときに再起動をトリガーできます (おそらく、JMX などを介した監視)。

于 2010-03-24T20:59:24.073 に答える
0

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

于 2010-03-24T21:50:56.420 に答える