ケースと分析
問題の背景
Tomcatでは、次のコードがすべてWebApp内にあり、 WebappClassLoaderが漏れてリサイクルできません。
パブリッククラスmycounter {private int count = 0; public void increment(){count ++; } public int getCount(){return count; }} public class mythreadlocal extends threadlocal <mycounter> {} public class LeakingServletはhttpservletを拡張します{private static mythreadlocal mythreadlocal = new mythreadlocal();保護されたvoid doget(httpservletrequest request、httpservletresponse応答)servletexception、ioexception {mycounter counter = mythreadlocal.get(); if(counter == null){counter = new MyCounter(); mythreadlocal.set(counter); } response.getWriter()。println( "現在のスレッドはこのサーブレットを提供しました" + counter.getCount() + "times"); counter.increment(); }}上記のコードでは、 LeakingServletが1回呼び出され、実行するスレッドが停止しない限り、 WebappClassLoaderリークが発生します。アプリケーションreloadたびに、追加のWebappClassLoaderインスタンスが表示され、最終的にPermGen OutOfMemoryExceptionにつながります。
問題を解決します
それでは、それについて考えてみましょう:なぜ上記のThreadLocalサブクラスがメモリリークを引き起こすのですか?
WebAppClassLoader
まず第一に、 WebappClassLoaderとは何かを理解する必要がありますか?
Java EEコンテナで実行されているWebアプリケーションの場合、クラスローダーの実装は一般的なJavaアプリケーションの実装とは異なります。異なるWebコンテナも異なる方法で実装されます。 Apache Tomcatでは、各Webアプリケーションには対応するクラスローダーインスタンスがあります。このクラスローダーもプロキシモードを使用します。違いは、最初に特定のクラスをロードしようとし、次に見つけられない場合は親クラスローダーにプロキシであることです。これは、一般クラスローダーの順序の反対です。これは、Javaサーブレットの仕様で推奨される練習であり、その目的は、WebアプリケーションのクラスにWebコンテナが提供するクラスよりも優先順位を付けることです。このプロキシパターンの例外は、Javaコアライブラリのクラスが検索の範囲内ではないことです。これは、Java Core Libraryのタイプの安全性を確保するためのものです。
言い換えれば、 WebappClassLoader 、TomcatがWebAppsをロードするためのカスタムクラスローダーです。各WebAppのクラスローダーは異なります。これは、異なるアプリケーションによってロードされたクラスを分離することです。
それでは、 WebappClassLoaderの機能はメモリリークと何の関係があるのでしょうか?まだ表示されていませんが、私たちの注意に値する非常に重要な機能です。各WebAppには、Java Coreクラスローダーとは異なる独自のWebappClassLoaderがあります。
WebappClassLoaderリークは、他のオブジェクトによって強く参照されているためであるため、参照関係図を描画しようとすることができることを知っています。等!クラスローダーは正確に何が機能しますか?なぜそれは引用を強いられているのですか?
クラスのライフサイクルとクラスローダー
上記の問題を解決するには、クラスのライフサイクルとクラスローダーの関係を調査する必要があります。
私たちのケースに関連する主なことは、クラスのアンインストールです。
クラスが使用された後、次の状況が満たされた場合、クラスはアンインストールされます。
1.このクラスのすべてのインスタンスはリサイクルされています。つまり、このクラスのインスタンスはJava Heapには存在しません。
2。このクラスのロードされたClassLoaderはリサイクルされています。
3.このクラスに対応するjava.lang.Classオブジェクトはどこにも参照されておらず、どこでも反射を介してクラスにアクセスする方法はありません。
上記の3つの条件すべてが満たされている場合、JVMはメソッド領域のガベージコレクション中にクラスをアンインストールします。クラスのアンインストールプロセスは、実際にメソッド領域のクラス情報をクリアすることであり、Javaクラスのライフサイクル全体が終了します。
Java仮想マシンに付属のクラスローダーがロードされたクラスは、仮想マシンのライフサイクル中にアンインストールされることはありません。 Java仮想マシンが付属するクラスローダーには、ルートクラスローダー、拡張クラスローダー、システムクラスローダーが含まれます。 Java仮想マシン自体は常にこれらのクラスローダーを参照しており、これらのクラスローダーは常にロードされるクラスのクラスオブジェクトを参照するため、これらのクラスオブジェクトは常にアクセス可能です。
ユーザー定義のクラスローダーによってロードされたクラスは、アンロードできます。
上記の文に注意してください。 WebappClassLoaderが漏れている場合、ロードするクラスPermGen OutOfMemoryExceptionアンインストールできないことを意味します。
キーポイントの下の写真を見てください
クラスローダーオブジェクトは、ロードするクラスオブジェクトに双方向に関連付けられていることがわかります。これは、クラスオブジェクトがWebappClassLoaderへの強制的な参照の原因であり、漏れを引き起こすことを意味します。
引用関係図
クラスローダーとクラスのライフサイクルとの関係を理解した後、参照関係図の描画を開始できます。 (図のLeakingServlet.classとmyThreadLocal 、主にmyThreadLocalがクラス変数であることを表現するために、参照図では厳密ではありません)
以下に、上記の図に基づいてWebappClassLoader漏れの原因を分析します。
1. LeakingServlet static MyThreadLocalを保持し、 LeakingServletクラスのライフサイクルである限り、 myThreadLocalのライフサイクルをもたらします。これは、 myThreadLocalがリサイクルされず、弱い参照が役に立たないことを意味するため、現在のスレッドはThreadLocalMapの保護対策を介してカウンターの強力な参照をクリアすることはできません。
2。強力な参照チェーン: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader 、 WebappClassLoaderが漏れます。
要約します
メモリリークは検出が難しく、多くの理由によって引き起こされることがよくあります。 Threadlocalは、ライフサイクルがスレッドに縛られているため、メモリリークを頻繁に訪れることができ、少し不注意が災害につながります。この記事は、特定のケースの分析にすぎません。これを使用して他のケースに適用できる場合は、優れています。この記事がすべての人に役立つことを願っています。