ホームページ >Java >&#&チュートリアル >Javaでメモリリークを引き起こすコードを記述する方法

Javaでメモリリークを引き起こすコードを記述する方法

伊谢尔伦
伊谢尔伦オリジナル
2016-11-25 10:42:131527ブラウズ

このテキストは、StackOverflow Q&A Web サイトで人気のあるディスカッション「メモリ リークを引き起こす Java でコードを記述する方法」から引用したものです。

Q: 面接に参加したところ、面接官がメモリリークを引き起こす Java コードの書き方を尋ねました。この質問については全く分かりません、とても恥ずかしいです。

A1: メモリ リークは、次の手順で簡単に発生する可能性があります (プログラム コードは特定のオブジェクトにアクセスできませんが、オブジェクトはメモリに保存されています):

アプリケーションは、長時間実行されるスレッドを作成します (または、スレッド プールを使用します。漏れが早く発生します)。

スレッドは、特定のクラスローダー (カスタマイズ可能) を通じてクラスをロードします。

このクラスは、大きなメモリ ブロック (new byte[1000000] など) を割り当て、静的変数に強参照を格納してから、独自の参照を ThreadLocal に格納します。追加のメモリ new byte[1000000] の割り当てはオプションです (クラス インスタンス リークで十分です)。ただし、これによりメモリ リークが速くなります。

スレッドは、カスタム クラスまたはクラスをロードするクラス ローダーをクリーンアップします。

上記の手順を繰り返します。

クラスおよびクラスローダーへの参照がないため、ThreadLocalのストレージにアクセスできません。 ThreadLocal はオブジェクトへの参照を保持し、クラスとそのクラス ローダーへの参照も保持します。そのため、クラス ローダーはロードするクラスへのすべての参照を保持するため、GC は ThreadLocal に格納されているメモリを再利用できません。多くの JVM 実装では、Java クラスとクラス ローダーは GC を実行せずに permgen 領域に直接割り当てられるため、より深刻なメモリ リークが発生します。

このリーク パターンの変形の 1 つは、ThreadLocal を任意の形式で使用するアプリケーションやアプリケーション コンテナー (Tomcat など) を頻繁に再デプロイすると、メモリ リークが容易に発生することです (アプリケーション コンテナーは前述のようにスレッドを使用するため、新しいクラスローダーがアプリが再デプロイされるたびに使用されます)。

A2:

静的変数参照オブジェクト

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

長い文字列のString.intern()を呼び出す

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you cant remove
str.intern();

開いたストリーム(ファイル、ネットワークなど)は閉じられていない

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

接続は閉じられていない

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

JVMのGCが利用できない領域に到達

例えば、ネイティブメソッドで割り当てられたメモリ。

アプリケーションスコープ内のWebアプリケーションのオブジェクト、アプリケーションは再起動されていないか、明示的に削除されていません

getServletContext().setAttribute("SOME_MAP", map);

セッションスコープ内のWebアプリケーションのオブジェクトは、無効化されていない、または明示的に削除されていない 削除

session.setAttribute("SOME_MAP", map);

不正または不適切なJVMオプション

例えば、IBM JDKのnoclassgcは、無駄なクラスのガベージコレクションを防ぎます

A3: HashSetの場合hashCode() または equals() が正しく実装されていない (または実装されていない) と、「コピー」がコレクションに追加され続けます。コレクションが無視すべき要素を無視できない場合、コレクションのサイズは増大し続けるだけであり、これらの要素は削除できません。

間違ったキーと値のペアを生成したい場合は、次のようにすることができます:

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}
  
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.

A4: 忘れられたリスナー、静的参照、ハッシュマップ内のキーエラー/変更に加えて、ライフサイクルを終了できないスレッドブロック、メモリ リークのシナリオ。ここでは、主にスレッドに関連する、それほど明白ではない Java メモリ リークのシナリオをいくつか紹介します。

Runtime.addShutdownHook は削除されません。removeShutdownHook を使用しても、未起動スレッドに対する ThreadGroup クラスのバグにより、リサイクルされず、ThreadGroup でメモリ リークが発生する可能性があります。

スレッドは作成されるが開始されない、上記と同じ状況

ContextClassLoader と AccessControlContext を継承するスレッドの作成、ThreadGroup と InheritedThreadLocal の使用、これらすべての参照は潜在的なリークであり、クラス ローダーによってロードされたすべてのクラスと同様に、静的参照など。これは、j.u.c.Executor フレームワーク (java.util.concurrent) 全体の重要なコンポーネントである ThreadFactory インターフェイスに非常に明白な影響を与えますが、多くの開発者はその潜在的な危険性に気づいていません。そして多くのライブラリはリクエストに応じてスレッドを開始します。

ThreadLocal キャッシュは多くの場合、良い習慣ではありません。 ThreadLocal に基づいた単純なキャッシュの実装は数多くありますが、スレッドが予想されるライフサイクルを超えて実行され続けると、ContextClassLoader がリークします。本当に必要な場合を除き、ThreadLocal キャッシュを使用しないでください。

ThreadGroup.destroy() は、ThreadGroup 自体にスレッドがなくても、子スレッド グループがまだある場合に呼び出されます。メモリ リークが発生すると、スレッド グループを親スレッド グループから削除できず、子スレッド グループを列挙できなくなります。

WeakHashMapを使用すると、値が直接(間接的に)キーを参照するため、見つけるのが困難になります。これは、Weak/SoftReference を継承するクラスが保護されたオブジェクトへの強い参照を保持する可能性がある場合にも当てはまります。

リソースをダウンロードするには、http(s)プロトコルのjava.net.URLを使用します。 KeepAliveCache はシステム ThreadGroup に新しいスレッドを作成し、現在のスレッドのコンテキスト クラス ローダー メモリのリークを引き起こします。スレッドは、生き残っているスレッドがない最初のリクエストで作成されるため、リークが発生する可能性が非常に高くなります。 (これは Java 7 で修正されており、スレッドを作成するコードはコンテキスト クラス ローダーを適切に削除します。)

InflaterInputStream を使用して、コンストラクター (PNGImageDecoder など) で新しい java.util.zip.Inflater() を渡します。インフレーターの end() は呼び出しません。 new だけは非常に安全ですが、コンストラクターのパラメーターとしてクラスを自分で作成し、ストリームの close() を呼び出してインフレーターを閉じることができない場合、メモリ リークが発生する可能性があります。これはファイナライザーによって解放されるため、実際にはメモリ リークではありません。しかし、これはネイティブ メモリを大量に消費するため、Linux の oom_killer がプロセスを強制終了させます。したがって、このことから得られる教訓は、ネイティブ リソースをできるだけ早くリリースするということです。

同じことが java.util.zip.Deflater にも当てはまりますが、これはさらに深刻です。デフレーターがほとんど使用されないのが良いことかもしれません。デフレーターまたはインフレーターを自分で作成する場合は、end() を呼び出す必要があることに注意してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。