Как очистить ThreadLocals - PullRequest
       55

Как очистить ThreadLocals

55 голосов
/ 06 октября 2010

У кого-нибудь есть пример, как это сделать?Они обрабатываются сборщиком мусора?Я использую Tomcat 6.

Ответы [ 7 ]

67 голосов
/ 06 октября 2010

Javadoc говорит это:

"Каждый поток содержит неявную ссылку на свою копию локальной переменной потока, пока поток является живым и экземпляр ThreadLocal доступен; после того, как поток уходит, все его копии локальных экземпляров потока подчиняются в сборщик мусора (если нет других ссылок на эти копии).

Если ваше приложение или (если вы говорите о потоках запроса) использует пул потоков, это означает, что потоки не умирают. При необходимости вам придется самостоятельно разбираться с нитками местных жителей. Единственный чистый способ сделать это - вызвать метод ThreadLocal.remove().

Существует две причины, по которым вы можете захотеть очистить локальные потоки для потоков в пуле потоков:

  • для предотвращения утечек памяти (или гипотетического ресурса), или
  • для предотвращения случайной утечки информации из одного запроса в другой через локальные потоки.

Утечки локальной памяти потоков обычно не должны быть основной проблемой для ограниченных пулов потоков, поскольку любые локальные потоки в конечном итоге могут быть перезаписаны; то есть когда нить используется повторно. Однако, если вы допустите ошибку создания новых экземпляров ThreadLocal снова и снова (вместо использования переменной static для хранения одноэлементного экземпляра), локальные значения потока не будут перезаписаны и будут накапливаться в каждом карта threadlocals темы. Это может привести к серьезной утечке.


Если вы говорите о локальных потоках, которые были созданы / использованы во время обработки HTTP-запроса веб-приложением, то одним из способов избежать локальных утечек потока является регистрация ServletRequestListener в ServletContext вашего веб-приложения и реализация requestDestroyed метод слушателя для очистки локальных потоков для текущего потока.

Обратите внимание, что в этом контексте вам также необходимо учитывать возможность утечки информации из одного запроса в другой.

33 голосов
/ 20 мая 2013

Вот некоторый код для очистки всех локальных переменных потока от текущего потока, когда у вас нет ссылки на фактическую локальную переменную потока. Вы также можете обобщить его для очистки локальных переменных потока для других потоков:

    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);
        }
    }
16 голосов
/ 25 января 2012

Нет способа очистить ThreadLocal значения, кроме как изнутри потока, который помещает их туда в первую очередь (или когда поток собирается мусором - не в случае с рабочими потоками). Это означает, что вы должны позаботиться о том, чтобы очистить свой ThreadLocal после завершения запроса сервлета (или перед передачей AsyncContext другому потоку в Servlet 3), потому что после этого момента у вас никогда не будет возможности войти в этот конкретный рабочий поток, и, следовательно, утечка памяти в ситуациях, когда ваше веб-приложение не развернуто, а сервер не перезапущен.

Хорошее место для такой очистки: ServletRequestListener.requestDestroyed () .

Если вы используете Spring, вся необходимая проводка уже установлена, вы можете просто поместить вещи в область запроса, не беспокоясь об их очистке (это происходит автоматически):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);
1 голос
/ 01 января 2017

Внимательно прочитав документацию Javadoc:

'Каждый поток содержит неявную ссылку на свою копию локальной переменной потока, пока поток активен и экземпляр ThreadLocal доступен;после того, как поток уходит, все его копии локальных экземпляров потока подлежат сборке мусора (если не существует других ссылок на эти копии).'

Нет необходимости что-либо чистить, есть условие «И» для выживания утечки.Таким образом, даже в веб-контейнере, где поток сохраняется в приложении, до тех пор, пока класс webapp не выгружен (это может предотвратить только ссылка на статический класс, загруженный в загрузчик родительского класса, и это не имеет ничего общего с ThreadLocal, но общие проблемыобщие файлы jar со статическими данными), тогда вторая ветвь условия AND больше не выполняется, поэтому локальная копия потока может быть использована для сборки мусора.

Поток local не может быть причиной утечек памяти, посколькуреализация соответствует документации.

0 голосов
/ 12 ноября 2018

@ ответ lyaffe является наилучшим из возможных для Java 6. Есть несколько проблем, которые этот ответ решает, используя то, что доступно в Java 8.

@ ответ lyaffe был написан для Java 6 до того, как 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);
}
0 голосов
/ 10 апреля 2014

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>();
    .
    .
    .
}
0 голосов
/ 05 июня 2013

Я хотел бы внести свой ответ на этот вопрос, даже если он старый. Меня мучила та же проблема (локальный поток gson не удалялся из потока запроса), и я даже успел перезапустить сервер всякий раз, когда ему не хватило памяти (что отстой!).

В контексте веб-приложения java, настроенного на режим dev (в котором сервер настроен на сброс при каждом изменении кода и, возможно, также в режиме отладки), я быстро узнал, что threadlocals может быть потрясающим и иногда быть болью. Я использовал Threadlocal Invocation для каждого запроса. Внутри Invocation. Иногда я также использовал gson для генерации своего ответа. Я бы обернул Invocation внутри блока try в фильтре и уничтожил бы его внутри блока finally.

То, что я заметил (у меня пока нет метрик, подтверждающих это), это то, что если я внес изменения в несколько файлов и сервер постоянно подпрыгивал между изменениями, я бы потерял терпение и перезапустил сервер быть точным) из IDE. Скорее всего, в противном случае я бы получил исключение «Недостаточно памяти».

Как мне удалось обойти это, так это включить реализацию ServletRequestListener в мое приложение, и моя проблема исчезла. Я думаю, что происходило то, что в середине запроса, если сервер несколько раз подпрыгивал, мои локальные потоки не очищались (включая gson), поэтому я получал бы это предупреждение о локальных потоках и два или три предупреждения позже, сервер зависнет. Когда ServletResponseListener явно закрывал мои локальные потоки, проблема gson исчезла.

Я надеюсь, что это имеет смысл и дает вам представление о том, как преодолеть локальные проблемы с потоками. Всегда закрывайте их вокруг места их использования. В ServletRequestListener проверьте каждую локальную оболочку потока и, если она все еще имеет действительную ссылку на какой-либо объект, уничтожьте ее в этот момент.

Я должен также отметить, что принято включать в состав локальный поток как статическую переменную внутри класса. Таким образом, вы можете быть уверены, что, уничтожив его в ServeltRequestListener, вам не придется беспокоиться о других экземплярах того же класса, висящих вокруг.

...