70

これを行う方法の例はありますか?それらはガベージコレクターによって処理されますか?Tomcat6を使用しています。

4

7 に答える 7

82

javadocはこれを言います:

「各スレッドは、スレッドが生きていてThreadLocalインスタンスにアクセスできる限り、スレッドローカル変数のコピーへの暗黙の参照を保持します。スレッドがなくなると、スレッドローカルインスタンスのすべてのコピーがガベージコレクションの対象になります。 (これらのコピーへの他の参照が存在しない限り)。

アプリケーションまたは(リクエストスレッドについて話している場合)コンテナがスレッドプールを使用している場合、これはスレッドが停止しないことを意味します。必要に応じて、スレッドローカルを自分で処理する必要があります。これを行う唯一のクリーンな方法は、メソッドを呼び出すことThreadLocal.remove()です。

スレッドプール内のスレッドのローカルスレッドをクリーンアップする理由は2つあります。

  • メモリ(または仮想的にリソース)のリークを防ぐため、または
  • スレッドローカルを介して、あるリクエストから別のリクエストに誤って情報が漏洩するのを防ぐため。

スレッドローカルメモリリークは、通常、制限付きスレッドプールでは大きな問題にはなりません。これは、スレッドローカルが最終的に上書きされる可能性があるためです。つまり、スレッドが再利用されるとき。ThreadLocalただし、 (変数を使用してシングルトンインスタンスを保持する代わりに)新しいインスタンスを何度も作成するのを間違えたstatic場合、スレッドローカル値は上書きされず、各スレッドのthreadlocalsマップに蓄積されます。これにより、重大なリークが発生する可能性があります。


WebアプリケーションによるHTTPリクエストの処理中に作成/使用されるスレッドローカルについて話していると仮定すると、スレッドローカルリークを回避する1つの方法は、Webアプリケーションにを登録しServletRequestListenerServletContextリスナーのrequestDestroyedメソッドを実装してスレッドローカルをクリーンアップすることです。現在のスレッド。

このコンテキストでは、あるリクエストから別のリクエストに情報が漏洩する可能性も考慮する必要があることに注意してください。

于 2010-10-06T03:02:15.003 に答える
35

実際のスレッドローカル変数への参照がない場合に、現在のスレッドからすべてのスレッドローカル変数を削除するコードを次に示します。他のスレッドのスレッドローカル変数をクリーンアップするために一般化することもできます。

    private void cleanThreadLocals() {
        try {
            // Get a reference to the thread locals table of the current thread
            Thread thread = Thread.currentThread();
            Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
            threadLocalsField.setAccessible(true);
            Object threadLocalTable = threadLocalsField.get(thread);

            // Get a reference to the array holding the thread local variables inside the
            // ThreadLocalMap of the current thread
            Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
            Field tableField = threadLocalMapClass.getDeclaredField("table");
            tableField.setAccessible(true);
            Object table = tableField.get(threadLocalTable);

            // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
            // is a reference to the actual ThreadLocal variable
            Field referentField = Reference.class.getDeclaredField("referent");
            referentField.setAccessible(true);

            for (int i=0; i < Array.getLength(table); i++) {
                // Each entry in the table array of ThreadLocalMap is an Entry object
                // representing the thread local reference and its value
                Object entry = Array.get(table, i);
                if (entry != null) {
                    // Get a reference to the thread local object and remove it from the table
                    ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
                    threadLocal.remove();
                }
            }
        } catch(Exception e) {
            // We will tolerate an exception here and just log it
            throw new IllegalStateException(e);
        }
    }
于 2013-05-20T07:17:48.617 に答える
20

そもそも値をそこに置いたスレッド内から(またはスレッドがガベージコレクションされた場合-ワーカースレッドの場合ではなく)、ThreadLocal値をクリーンアップする方法はありません。つまり、サーブレットリクエストが終了したとき(またはサーブレット3の別のスレッドにAsyncContextを転送する前)に、ThreadLocalをクリーンアップするように注意する必要があります。それ以降は、その特定のワーカースレッドに入る機会がない可能性があるためです。サーバーが再起動されていないときにWebアプリがデプロイされていない状況では、メモリがリークします。

このようなクリーンアップを行うのに適した場所は、ServletRequestListener.requestDestroyed()です。

Springを使用している場合は、必要なすべての配線がすでに整っているので、クリーンアップを気にせずにリクエストスコープに入れることができます(自動的に行われます)。

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
于 2012-01-25T15:07:30.807 に答える
2

Javadocのドキュメントを注意深く読み直してください。

'各スレッドは、スレッドが生きていてThreadLocalインスタンスにアクセスできる限り、スレッドローカル変数のコピーへの暗黙の参照を保持します。スレッドがなくなると、スレッドローカルインスタンスのすべてのコピーがガベージコレクションの対象になります(これらのコピーへの他の参照が存在しない場合)。'

何も掃除する必要はありません。リークが存続するための「AND」条件があります。したがって、スレッドがアプリケーションに対して存続するWebコンテナでも、webappクラスがアンロードされている限り(親クラスローダーにロードされた静的クラスの参照のみがこれを防ぎ、これはThreadLocalとは関係ありませんが、静的データを使用する共有jar)の場合、AND条件の2番目のレッグが満たされなくなるため、スレッドローカルコピーはガベージコレクションの対象になります。

実装がドキュメントを満たしている限り、ローカルスレッドがメモリリークの原因になることはありません。

于 2017-01-01T13:38:01.900 に答える
1

この質問は古いですが、私の答えを提供したいと思います。私は同じ問題(gson threadlocalがリクエストスレッドから削除されない)に悩まされていました。また、サーバーがメモリ不足になったときはいつでもサーバーを再起動することに慣れていました(これは非常に時間がかかります!!)。

開発モードに設定されているJavaWebアプリのコンテキスト(サーバーがコードの変更を検出するたびにバウンスするように設定されており、デバッグモードでも実行されている可能性があります)では、threadlocalsが素晴らしい可能性があることをすぐに学びましたそして時々苦痛になります。私はすべてのリクエストにスレッドローカル呼び出しを使用していました。呼び出しの内部。応答を生成するためにgsonを使用することもあります。呼び出しをフィルターの「try」ブロック内にラップし、「finally」ブロック内で破棄します。

私が観察したこと(今のところこれをバックアップするためのメトリックはありません)は、いくつかのファイルに変更を加え、サーバーが変更の間に絶えずバウンスしている場合、焦ってサーバーを再起動することです(正確にはtomcat) IDEから。ほとんどの場合、「メモリ不足」の例外が発生します。

これを回避する方法は、アプリにServletRequestListener実装を含めることでしたが、問題はなくなりました。リクエストの途中で、サーバーが数回バウンスした場合、スレッドローカルがクリアされていなかったため(gsonを含む)、スレッドローカルに関するこの警告と2、3回の警告が発生したと思います。サーバーがクラッシュします。ServletResponseListenerがスレッドローカルを明示的に閉じると、gsonの問題はなくなりました。

これが理にかなっていて、スレッドローカルの問題を克服する方法のアイデアが得られることを願っています。常に使用場所の周りでそれらを閉じてください。ServletRequestListenerで、各スレッドローカルラッパーをテストし、オブジェクトへの有効な参照がまだある場合は、その時点で破棄します。

また、スレッドローカルを静的変数としてクラス内でラップすることを習慣にしていることも指摘しておく必要があります。そうすれば、ServeltRequestListenerで破棄することで、同じクラスの他のインスタンスがぶら下がっていることを心配する必要がないことが保証されます。

于 2013-06-05T06:45:16.963 に答える
0

JVMは、ThreadLocalオブジェクト内にあるすべての参照なしオブジェクトを自動的にクリーンアップします。

これらのオブジェクトをクリーンアップする別の方法(たとえば、これらのオブジェクトは、周りに存在するすべてのスレッドセーフでないオブジェクトである可能性があります)は、オブジェクトを基本的に保持するObject Holderクラス内に配置し、finalizeメソッドをオーバーライドしてオブジェクトをクリーンアップすることです。その中に存在します。finalizeこの場合も、メソッドを呼び出すタイミングはガベージコレクターとそのポリシーに依存します。

コードサンプルは次のとおりです。

public class MyObjectHolder {

    private MyObject myObject;

    public MyObjectHolder(MyObject myObj) {
        myObject = myObj;
    }

    public MyObject getMyObject() {
        return myObject;
    }

    protected void finalize() throws Throwable {
        myObject.cleanItUp();
    }
}

public class SomeOtherClass {
    static ThreadLocal<MyObjectHolder> threadLocal = new ThreadLocal<MyObjectHolder>();
    .
    .
    .
}
于 2014-04-10T03:51:22.940 に答える
0

@lyaffeの答えはJava6で可能な限り最良です。この答えが、Java8で利用可能なものを使用して解決するいくつかの問題があります。

@lyaffeの回答は、利用可能になる前にJava6用に作成されましたMethodHandle。リフレクションによるパフォーマンスの低下に悩まされています。以下のように使用すると、フィールドとメソッドへのオーバーヘッドアクセスがゼロMethodHandleになります。

@lyaffeの回答もThreadLocalMap.table明示的に通過し、バグが発生しやすくなります。ThreadLocalMap.expungeStaleEntries()同じことを行う方法が現在利用可能です。

以下のコードには、呼び出しのコストを最小限に抑えるための3つの初期化メソッドがありますexpungeStaleEntries()

private static final MethodHandle        s_getThreadLocals     = initThreadLocals();
private static final MethodHandle        s_expungeStaleEntries = initExpungeStaleEntries();
private static final ThreadLocal<Object> s_threadLocals        = ThreadLocal.withInitial(() -> getThreadLocals());

public static void expungeThreadLocalMap()
{
   Object threadLocals;

   threadLocals = s_threadLocals.get();

   try
   {
      s_expungeStaleEntries.invoke(threadLocals);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }
}

private static Object getThreadLocals()
{
   ThreadLocal<Object> local;
   Object result;
   Thread thread;

   local = new ThreadLocal<>();

   local.set(local);   // Force ThreadLocal to initialize Thread.threadLocals

   thread = Thread.currentThread();

   try
   {
      result = s_getThreadLocals.invoke(thread);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }

   return(result);
}

private static MethodHandle initThreadLocals()
{
   MethodHandle result;
   Field field;

   try
   {
      field = Thread.class.getDeclaredField("threadLocals");

      field.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflectGetter(field);

      result = Preconditions.verifyNotNull(result, "result is null");
   }
   catch (NoSuchFieldException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}

private static MethodHandle initExpungeStaleEntries()
{
   MethodHandle result;
   Class<?> clazz;
   Method method;
   Object threadLocals;

   threadLocals = getThreadLocals();
   clazz        = threadLocals.getClass();

   try
   {
      method = clazz.getDeclaredMethod("expungeStaleEntries");

      method.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflect(method);
   }
   catch (NoSuchMethodException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}
于 2018-11-12T15:19:11.203 に答える