2013-07-31 6 views
40

Указано на нескольких постах: неправильное использование ThreadLocal вызывает утечку памяти. Я изо всех сил пытаюсь понять, как произойдет утечка памяти с помощью ThreadLocal.ThreadLocal & Memory Leak

Единственный сценарий, я понял, что, как показано ниже:

Веб-сервер поддерживает пул потоков (например, для сервлетов). Эти потоки могут создавать утечку памяти, если переменные в ThreadLocal не удаляются, потому что потоки не умирают.

В этом сценарии не упоминается утечка памяти «Пермское пространство». Это единственный (основной) случай использования утечки памяти?

+0

Не могли бы вы немного разъяснить вопрос? Вы только хотите узнать, может ли ThreadLocal вызвать истощение PermGen? – MRalwasser

+0

Да. Я хочу знать - 1. Как ThreadLocal вызывает истощение PermGen. 2. Любой другой распространенный сценарий неправильного использования ThreadLocal, вызывающего утечку памяти. –

+1

Существует подробное объяснение по адресу http://java.jiderhamn.se/2012/01/29/classloader-leaks-iv-threadlocal-dangers-and-why-threadglobal-may-have-been-a-more -appro -name/Автор также предлагает интересную библиотеку предотвращения утечки. – Pino

ответ

58

PermGen исчерпание в сочетании с ThreadLocal часто вызван ClassLoader утечкой.

Пример:
Представьте, сервер приложений, который имеет пул рабочих потоков .
Они будут поддерживаться до тех пор, пока не прекратится действие сервера приложений.
Развернутое веб-приложение использует статическийThreadLocal в одном из своих классов, чтобы хранить некоторые локальные данные потока, экземпляр другого класса (позволяет называть его SomeClass) веб-приложения. Это делается внутри рабочего потока (например, это действие происходит от HTTP-запроса ).

Важно:
By definition, ссылка на значение в ThreadLocalсохраняется до тех пор, «владеющим» поток не умирает, или если сам ThreadLocal больше не доступен.

Если веб-приложение не удается очистить ссылку к ThreadLocalна остановке, плохие вещи будут происходить:
Поскольку рабочий поток обычно никогда не умирают, и ссылка на ThreadLocal статична, значение ThreadLocalвсе еще ссылается на экземпляр SomeClass, класс веб-приложения - , даже если веб-приложение было остановлено!

Как следствие, Загрузчик классов веб-приложения не может быть мусора, что означает, что все классы (и все статические данные) из веб-приложения остаются загружены (это влияет на пул памяти PermGen, а также куча).
Каждая итерация повторного развертывания веб-приложения увеличит использование гипермен (и кучи).

=> Это PermGen утечка

Один популярный пример такого рода утечки является this bug в log4j (фиксируется в это время).

+2

Вопрос (может быть, немой): почему веб-сервер не пытается убить обработанные потоки для этого приложения, когда приложение остановлено? Я бы предположил/предположил, что веб-сервер создаст рабочий поток, специфичный для приложения. Если веб-сервер не убивает эти потоки, даже в случае отсутствия ThreadLocal потоки остаются там. –

+1

Нити никогда не должны быть убиты, их следует только уведомлять/прерывать, чтобы не прерывать себя самостоятельно. Кроме того, потоки дороги для создания и часто используются для нескольких приложений в одном контейнере, но это специфично для реализации. Однако некоторые серверы приложений отбрасывают все потоки остановленного веб-приложения (в зависимости от продукта/конфигурации) или периодически обновляют свои потоки, чтобы предотвратить утечку. Это тоже специфичная реализация. Взгляните на детали для tomcat: http://tomcat.apache.org/tomcat-7.0-doc/config/executor.html – MRalwasser

+0

@MRalwasser: Thnaks для приятного объяснения. Понятно, почему treadlocal veerable не будет собираться мусором, но почему классный загрузчик и другие классы также не собираются собирать мусор? другим классам не нужно делать naything с классом, который имеет переменную threadlocal – Victor

1

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

Обратитесь к этому link Джошуа Блох

+0

Я упомянул в первом заявлении - если ThreadLocal неправильно используется. –

0

Предыдущие сообщения объясняют проблему, но не предоставляют никаких решений. Я обнаружил, что нет возможности «очистить» ThreadLocal. В контейнерной среде, где я обрабатываю запросы, я, наконец, только что вызвал .remove() в конце каждого запроса. Я понимаю, что это может быть проблематично с помощью транзакций, управляемых контейнерами.

23

Принятый ответ на этот вопрос и «суровые» журналы от Tomcat об этой проблеме вводят в заблуждение. Ключевая цитата есть:

По определению, ссылка на ThreadLocal значение сохраняется до тех пор, «владеющим» поток не умирает или если сам ThreadLocal больше не доступен. [Мой акцент].

В этом случае единственные ссылки на ThreadLocal находятся в статическом конечном поле класса, ставшего теперь целью для GC, и ссылкой из рабочих потоков. Однако ссылки из рабочих потоков на ThreadLocal: WeakReferences!

Значения ThreadLocal не являются слабыми ссылками. Итак, если у вас есть ссылки в значениях классов ThreadLocal для приложений, то они будут поддерживать ссылку на ClassLoader и предотвращать GC. Однако, если ваши значения ThreadLocal являются целыми числами или строками или некоторым другим базовым типом объекта (например, стандартным набором из вышеперечисленного), тогда не должно быть проблемы (они будут только предотвращать GC загрузочного/системного загрузчика классов, что никогда не произойдет в любом случае).

По-прежнему хорошей практикой является явная очистка ThreadLocal, когда вы закончите с ней, но в случае the cited log4j bug небо не было точно падающим (как вы можете видеть из отчета, значение представляет собой пустой Hashtable) ,

Вот какой код для демонстрации. Во-первых, мы создаем базовую реализацию пользовательского загрузчика классов без родителя, который печатает в System.out на доработке:

import java.net.*; 

public class CustomClassLoader extends URLClassLoader { 

    public CustomClassLoader(URL... urls) { 
     super(urls, null); 
    } 

    @Override 
    protected void finalize() { 
     System.out.println("*** CustomClassLoader finalized!"); 
    } 
} 

Затем мы определим приложение драйвера, который создает новый экземпляр этого класса загрузчика, использует его, чтобы загрузить класс с ThreadLocal, а затем удалите ссылку на загрузчик классов, позволяя ей быть GC'ed.Во-первых, в том случае, когда ThreadLocal значение является ссылкой к классу загруженного пользовательского загрузчика классов:

import java.net.*; 

public class Main { 

    public static void main(String...args) throws Exception { 
     loadFoo(); 
     while (true) { 
      System.gc(); 
      Thread.sleep(1000); 
     } 
    } 

    private static void loadFoo() throws Exception { 
     CustomClassLoader cl = new CustomClassLoader(new URL("file:/tmp/")); 
     Class<?> clazz = cl.loadClass("Main$Foo"); 
     clazz.newInstance(); 
     cl = null; 
    } 


    public static class Foo { 
     private static final ThreadLocal<Foo> tl = new ThreadLocal<Foo>(); 

     public Foo() { 
      tl.set(this); 
      System.out.println("ClassLoader: " + this.getClass().getClassLoader()); 
     } 
    } 
} 

Когда мы запустим это, мы можем видеть, что CustomClassLoader действительно не сборщиком мусора (как нить локальный основной поток имеет ссылку на экземпляр Foo, который был загружен наш пользовательский загрузчик классов):

 
$ java Main 
ClassLoader: [email protected] 

Однако, когда мы изменяем ThreadLocal вместо этого содержать ссылку на простой Integer, а не экземпляр Foo:

public static class Foo { 
    private static final ThreadLocal<Integer> tl = new ThreadLocal<Integer>(); 

    public Foo() { 
     tl.set(42); 
     System.out.println("ClassLoader: " + this.getClass().getClassLoader()); 
    } 
} 

Тогда мы видим, что пользовательский загрузчик классов является в настоящее время сборки мусора (как нить локальное в главном потоке имеет только ссылку на целое число, нагруженной системы загрузчиком классов):

 
$ java Main 
ClassLoader: [email protected] 
*** CustomClassLoader finalized! 

(То же самое true с Hashtable). Поэтому в случае log4j у них не было утечки памяти или какой-либо ошибки. Они уже очищали Hashtable, и этого было достаточно для обеспечения GC загрузчика классов. IMO, ошибка находится в Tomcat, которая без разбора регистрирует эти ошибки «SEVERE» при выключении для всех ThreadLocals, которые явно не были .remove() d, независимо от того, содержат ли они сильную ссылку на класс приложения или нет. Похоже, что, по крайней мере, некоторые разработчики тратят время и силы на «исправление» фантомных утечек памяти, скажем, на небрежные журналы Tomcat.

+0

как его очистить? – Dejell

+1

Иногда люди используют собственный класс в threadLocal, даже не осознавая этого. Например, это может произойти, когда вы используете инициализацию двойной скобки для локального значения потока, потому что инициализация двойной скобки создаст анонимный класс. Я создал исправление для локального потока, которое исправляет утечку загрузчика класса для ваших веб-приложений, но сохраняет неблокирующий доступ к локальным значениям потока: github.com/codesinthedark/ImprovedThreadLocal Теперь вы можете использовать свои классы в ThreadLocal, и вы не будете есть утечка памяти при переадресации webapp – user1944408

+0

Это было полезно - так ответ, который мы не должны указывать для этой утилиты или есть способ предотвратить утечку памяти такого рода. – jtkSource

0

Ниже код, экземпляр t для итерации не может быть GC. Это может быть примером ThreadLocal & Memory Leak

public class MemoryLeak { 

    public static void main(String[] args) { 
     new Thread(new Runnable() { 
      @Override 
      public void run() { 
       for (int i = 0; i < 100000; i++) { 
        TestClass t = new TestClass(i); 
        t.printId(); 
        t = null; 
       } 
      } 
     }).start(); 
    } 


    static class TestClass{ 
     private int id; 
     private int[] arr; 
     private ThreadLocal<TestClass> threadLocal; 
     TestClass(int id){ 
      this.id = id; 
      arr = new int[1000000]; 
      threadLocal = new ThreadLocal<>(); 
      threadLocal.set(this); 
     } 

     public void printId(){ 
      System.out.println(threadLocal.get().id); 
     } 
    } 
} 
0

Вот альтернатива ThreadLocal, которая не имеет проблему утечки памяти:

class BetterThreadLocal<A> { 
    Map<Thread, A> map = Collections.synchronizedMap(new WeakHashMap()); 

    A get() { 
    ret map.get(Thread.currentThread()); 
    } 

    void set(A a) { 
    if (a == null) 
     map.remove(Thread.currentThread()); 
    else 
     map.put(Thread.currentThread(), a); 
    } 
} 

Примечание: Там есть новый сценарий утечки памяти, но это маловероятно, и его можно избежать, следуя простой направляющей линии. Этот сценарий поддерживает сильную ссылку на объект Thread в BetterThreadLocal.

Я никогда не буду поддерживать ссылки на темы, потому что вы всегда хотите, чтобы поток был GC'd, когда его работа была выполнена ... так что вы идете: память без протечек ThreadLocal.

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

Sample program in JavaX.

И последнее замечание: Моя система (JavaX) также отслеживает все WeakHashMaps и очищает их регулярно, так что последний супер-вряд ли отверстие подключен (долгоживущие WeakHashMaps, которые никогда не запрошенные, но все еще имеют устаревшие записи).