Принятый ответ на этот вопрос и «суровые» журналы от 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.
Не могли бы вы немного разъяснить вопрос? Вы только хотите узнать, может ли ThreadLocal вызвать истощение PermGen? – MRalwasser
Да. Я хочу знать - 1. Как ThreadLocal вызывает истощение PermGen. 2. Любой другой распространенный сценарий неправильного использования ThreadLocal, вызывающего утечку памяти. –
Существует подробное объяснение по адресу http://java.jiderhamn.se/2012/01/29/classloader-leaks-iv-threadlocal-dangers-and-why-threadglobal-may-have-been-a-more -appro -name/Автор также предлагает интересную библиотеку предотвращения утечки. – Pino