Javaメモリリークを見つける方法


142

JHatなどを使用して、Javaのメモリリークをどのように見つけますか?基本的な見方をするために、JHatにヒープダンプをロードしてみました。ただし、ルート参照(ref)またはそれが呼び出されるものを見つける方法を理解していません。基本的に、数百メガバイトのハッシュテーブルエントリ([java.util.HashMap $ Entryまたはそのようなもの)があることがわかりますが、マップは至る所で使用されています...大きなマップを検索する方法はありますか? 、またはおそらく大きなオブジェクトツリーの一般的なルートを見つけますか?

[編集]わかりました。これまでに回答を読みましたが、私は安い野郎(JProfilerの支払いよりもJHatの使い方を学ぶことに興味がある)だとだけ言っておきましょう。また、JHatはJDKの一部であるため、いつでも利用できます。もちろん、JHat以外には方法はありませんが、力ずくではありませんが、それが事実であるとは信じられません。

また、実際に変更(すべてのマップサイズのログを追加)して、リークに気付くのに十分な時間実行することはできないと思います。


これはJProfilerの別の「投票」です。ヒープ分析には非常によく機能し、適切なUIがあり、非常によく機能します。McKenzieG1が言うように、500ドルは、これらのリークの原因を探すために他の方法で焼く時間よりも安価です。ツールの価格に関しては、悪くはありません。
joev

:Oracleはここでは関係のページがあるdocs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/...
ローレル

回答:


126

Javaでメモリリークを見つけるには、次の方法を使用します。私はjProfilerを使用して大きな成功を収めましたが、グラフ作成機能(diffの方がグラフィカル形式で分析する方が簡単です)を備えた特殊なツールが機能すると思います。

  1. アプリケーションを起動し、すべての初期化が完了してアプリケーションがアイドル状態になっている「安定」状態になるまで待ちます。
  2. メモリリークの発生が疑われる操作を数回実行して、キャッシュ、DB関連の初期化を実行できるようにします。
  3. GCを実行し、メモリのスナップショットを取得します。
  4. 操作を再実行してください。操作の複雑さと処理されるデータのサイズによっては、操作を数回から数回実行する必要がある場合があります。
  5. GCを実行し、メモリのスナップショットを取得します。
  6. 2つのスナップショットの差分を実行して分析します。

基本的に、分析は、たとえばオブジェクトタイプによる最大の正の差分から開始し、それらの余分なオブジェクトがメモリに固定される原因を見つける必要があります。

複数のスレッドで要求を処理するWebアプリケーションの場合、分析はより複雑になりますが、それでも一般的なアプローチが適用されます。

特にアプリケーションのメモリフットプリントを削減することを目的とした多数のプロジェクトを実行しました。この一般的なアプローチは、アプリケーション固有の微調整とトリックを常にうまく機能させました。


7
ほとんど(すべてではない)のJavaプロファイラーには、ボタンをクリックするだけでGCを呼び出すオプションがあります。または、コードの適切な場所からSystem.gc()を呼び出すことができます。
Dima Malenko、2010年

3
System.gc()を呼び出しても、JVMは呼び出しを無視することを選択する場合があります。これはJVM固有の問題です。答えに+1。
Aniket Thakur

4
「メモリスナップショット」とは正確には何ですか?コードが実行している各オブジェクトタイプの数を教えてくれるものはありますか?
無視

2
「オブジェクトタイプごとの最大の正の差分から始める」から「これらの余分なオブジェクトがメモリに固定される原因を見つける」にはどうすればよいですか?int []、Object []、Stringなどの非常に一般的なものを見つけました。それらがどこから来たのかをどうやって見つけるのですか?
Vituel

48

ここで質問者、私はクリックに答えるのに5分もかからないツールを入手すると、潜在的なメモリリークを見つけるのがずっと簡単になると言わざるを得ません。

人々がいくつかのツールを提案しているので(JDKとJProbeの試用版でそれを得たので、ビジュアルwmのみを試しました)Eclipseプラットフォーム上に構築されたフリー/オープンソースツールであるMemory Analyzer(SAPメモリとして参照されることもある)を提案する必要がありますアナライザー)http://www.eclipse.org/mat/で入手できます

このツールの本当にクールな点は、最初に開いたときにヒープダンプにインデックスを付け、オブジェクトごとに5分待たずに保持されたヒープのようなデータを表示できることです(すべての操作は、私が試した他のツールよりもかなり高速でした)。 。

ダンプを開くと、最初の画面に最大のオブジェクト(保持されたヒープを数える)を含む円グラフが表示され、快適さのために大きいオブジェクトまですばやく移動できます。また、Recconが役立つ可能性のあるFindリークの疑いもありますが、ナビゲーションは十分だったので、実際には入りませんでした。


1
注目に値する:明らかにJava 5以降では、HeapDumpOnCtrlBreakVMパラメーターは使用できません。私が見つけた解決策(これまでのところ、まだ探している)は、JMapを使用して.hprofファイルをダンプし、それをEclipseに入れて、MATを使用して調べます。
Ben

1
ヒープダンプの取得に関して、ほとんどのプロファイラー(JVisualVMを含む)には、ヒープとスレッドの両方をファイルにダンプするオプションが含まれています。
bbaja42 2012年

13

ツールは大きな助けです。

ただし、ツールを使用できない場合があります。ヒープダンプが非常に大きいため、ツールがクラッシュしたり、シェルアクセスしかできない一部の本番環境でマシンをトラブルシューティングしたりする場合などです。

その場合は、hprofダンプファイルを回避する方法を知っていると役立ちます。

SITES BEGINを探します。これにより、どのオブジェクトが最もメモリを使用しているかがわかります。ただし、オブジェクトはタイプごとにまとめられるわけではありません。各エントリには「トレース」IDも含まれます。次に、その「TRACE nnnn」を検索して、オブジェクトが割り当てられたスタックの上位数フレームを確認できます。多くの場合、オブジェクトが割り当てられている場所を確認すると、バグを見つけて完了します。また、-Xrunhprofのオプションを使用して、スタックに記録するフレーム数を制御できることに注意してください。

割り当てサイトをチェックアウトして問題がなければ、予期しない参照チェーンを見つけるために、これらのライブオブジェクトの一部からルートオブジェクトへの逆方向チェーンを開始する必要があります。これはツールが本当に役立つ場所ですが、同じことを手動で行うことができます(まあ、grepを使用します)。ルートオブジェクトは1つだけではありません(つまり、ガベージコレクションの対象とならないオブジェクト)。スレッド、クラス、スタックフレームはルートオブジェクトとして機能し、それらが強く参照するものは収集できません。

連鎖を行うには、HEAP DUMPセクションで、トレースIDが正しくないエントリを探します。これにより、OBJまたはARRエントリが表示され、一意のオブジェクト識別子が16進数で表示されます。そのIDのすべての出現を検索して、オブジェクトへの強い参照を持っている人を見つけます。リークがどこにあるかがわかるまで、これらの各パスを逆方向にたどります。ツールがとても便利な理由をご覧ください。

静的メンバーは、メモリリークの繰り返しの違反者です。実際、ツールがなくても、コードを調べて静的なMapメンバーを探すのに数分を費やす価値があります。地図を大きくすることはできますか?エントリをクリーンアップするものはありますか?


私がチェックし、-last「ヒープ・ダンプは、それがツールをクラッシュとても巨大である」jhatMAT明らかにメモリに全体のヒープ・ダンプをロードしようとし、そのため一般的でクラッシュOutOfMemoryError最もヒープ分析を必要とするアプリケーションから、すなわち大型ダンプ(に! )。NetBeansプロファイラは、参照のインデックス作成に別のアルゴリズムを使用しているようです。これは、大きなダンプでは遅くなる可能性がありますが、少なくともツールの無制限のメモリを消費してクラッシュすることはありません。
Jesse Glick

10

ほとんどの場合、エンタープライズアプリケーションでは、指定されたJavaヒープは理想的な最大サイズの12〜16 GBよりも大きくなります。これらの大きなjavaアプリでNetBeansプロファイラーを直接動作させるのは難しいと思いました。

しかし、通常これは必要ありません。jdkに付属のjmapユーティリティを使用して「ライブ」ヒープダンプを取得できます。つまり、jmapはGCの実行後にヒープをダンプします。アプリケーションで何らかの操作を行い、操作が完了するまで待ってから、別の「ライブ」ヒープダンプを取得します。Eclipse MATなどのツールを使用して、ヒープダンプをロードし、ヒストグラムでソートし、増加したオブジェクト、または最も高いオブジェクトを確認します。これは手掛かりになります。

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

このアプローチには1つだけ問題があります。ライブオプションを使用した場合でも、巨大なヒープダンプは大きすぎて開発ラップに転送できず、開くには十分なメモリ/ RAMが搭載されたマシンが必要になる場合があります。

ここでクラスヒストグラムが重要になります。jmapツールを使用して、ライブクラスヒストグラムをダンプできます。これは、メモリ使用量のクラスヒストグラムのみを提供します。基本的に、参照をチェーンするための情報はありません。たとえば、char配列を先頭に配置する場合があります。そして、どこか下のStringクラス。自分で接続を描く必要があります。

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

2つのヒープダンプを取得する代わりに、上記のように2つのクラスヒストグラムを取得します。次に、クラスのヒストグラムを比較して、増加しているクラスを確認します。Javaクラスをアプリケーションクラスに関連付けることができるかどうかを確認します。これはかなり良いヒントになります。2つのjmapヒストグラムダンプを比較するのに役立つpythonスクリプトを次に示します。histogramparser.py

最後に、JConolseやVisualVmなどのツールは、時間の経過に伴うメモリの増加を確認し、メモリリークがないかどうかを確認するために不可欠です。最後に、あなたの問題はメモリリークではないかもしれませんが、メモリ使用量が高いかもしれません。これのために、GCロギングを有効にします。そして、jstatなどのjdkツールを使用して、GCの動作をライブで確認できます。

jstat -gccause pid <optional time interval>

-jhat、jmap、フルGC、Humongous割り当て、G1GCに関するその他のGoogleへの参照


1
ここでは、より詳細でブログ記事を追加- alexpunnen.blogspot.in/2015/06/...
アレックスPunnen

5

JProbe、YourKit、AD4J、JRockit Mission Controlなど、リークの発見に役立つツールがあります。最後は私が個人的に一番よく知っているものです。どのような優れたツールでも、どのリークが簡単に特定でき、リークオブジェクトが割り当てられているレベルまでドリルダウンできるはずです。

HashTables、Hashmaps、または同様のものを使用することは、Javaでメモリを手作業でリークすることができる数少ない方法の1つです。手でリークを見つける必要がある場合は、HashMapのサイズを定期的に印刷し、そこからアイテムを追加して削除するのを忘れたものを見つけます。


4

ええと、マップを変更するときにマップのサイズのログを追加し、マップが適切なサイズを超えて成長しているログを検索するというローテクソリューションは常に存在します。


1

NetBeansにはプロファイラーが組み込まれています。


0

割り当てを追跡するメモリプロファイラーを使用する必要があります。JProfilerを見てください。「ヒープウォーカー」機能は優れており、主要なJava IDEのすべてと統合されています。それは無料ではありませんが、それほど高価でもありません(1つのライセンスで$ 499)-あまり洗練されていないツールでリークを見つけるのに苦労している$ 500相当の時間を費やします。


0

ガベージコレクターを複数回呼び出した後、メモリ使用量のサイズを測定することで確認できます。

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

出力数が等しい場合、アプリケーションでメモリリークはありませんが、メモリ使用量の数(増加する数)の違いが見られる場合、プロジェクトでメモリリークがあります。例えば:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

ストリームやソケットなどのアクションによってメモリを解放するのに時間がかかる場合があることに注意しください。最初の出力で判断すべきではありません。特定の時間でテストする必要があります。


0

JProfilerを使用したメモリリークの検出について、この画面キャストを確認してください。@Dima Malenko Answerの視覚的な説明です。

注:JProfilerはフリーウェアではありませんが、試用版は現在の状況に対応できます。


0

私たちの多くはコードの記述にすでにEclipseを使用しているので、EclipseでMemory Analyzer Tool(MAT)を使用しないのはなぜですか。それは素晴らしい働きをします。

EclipseのMATを分析するためのツールを提供するのEclipse IDE用のプラグインのセットであるheap dumpsJavaアプリケーションからと識別するためmemory problemsのアプリケーションで。

これにより、開発者は次の機能でメモリリークを見つけることができます。

  1. メモリスナップショットの取得(ヒープダンプ)
  2. ヒストグラム
  3. 保持ヒープ
  4. ドミネーターツリー
  5. GCルートへのパスの探索
  6. 検査官
  7. 共通メモリアンチパターン
  8. オブジェクトクエリ言語

ここに画像の説明を入力してください

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.