Решение утечки приложения ClassLoader
Приложения как правило, хотят:
- Запускайте новые потоки, используя реализации Runnable из загрузчика классов приложения. Хотя модель программирования JEE этого не поддерживает, клиенты часто либо напрямую создают новые потоки, либо косвенно создают их, используя Таймер с. Клиенты должны убедиться, что эти потоки остановлены при остановке соответствующего приложения (или модуля WAR):
- javax.servlet.ServletContextListener.contextDestroyed может быть использован для уведомления о прекращении WAR для очистки. Обратите внимание, что WAR могут быть остановлены независимо от приложения, когда включена перезагрузка класса .
- Аннотации javax.ejb.Singleton , javax.ejb.Startup и javax.annotation.PreDestroy можно использовать для уведомления об остановке модуля EJB для его очистки. Обратите внимание, что одноэлементные EJB доступны только в EJB 3.1 (WAS v8).
- Startup bean-компоненты могут использоваться для уведомления, когда EJB-модуль останавливается для очистки. Все модули EJB останавливаются при остановке всего приложения.
- Используйте ThreadLocal (хранение ThreadLocal в статическом виде ). Значения ThreadLocal эффективно сохраняются как WeakHashMap в каждом потоке . Поскольку значения обычно включают объекты приложения, объект приложения ссылается на свой класс , который ссылается на свой ClassLoader , который ссылается на класс, содержащий ThreadLocal , слабая ссылка никогда не нарушается и возникает утечка.
Заказчикам рекомендуется либо избегать использования ThreadLocal , либо очищать ссылки на ThreadLocal при остановке модуля (см. Выше), либо гарантировать, что remove () вызывается после каждого запроса.
- Зарегистрируйте JMX MBeans или NotificationListener на сервере JMX. Клиенты должны убедиться, что они не зарегистрированы, когда соответствующее приложение (или модуль WAR) остановлено.
Произвольные компоненты
Сюда входят поставщики JDBC, стороннее программное обеспечение и приложения, которые хотят:
- Запустите новые потоки, включая «потоки таймера», созданные конструктором java.util.Timer . Когда поток создается, две части информации копируются из первичного потока:
- Загрузчик класса контекста ( getContextClassLoader () ). Когда приложение выполняется, контейнеры устанавливают загрузчик класса контекста на загрузчик класса модуля, поэтому вновь созданный поток будет поддерживать загрузчик класса контекста в течение всего времени его существования. Этого можно избежать, вызвав setContextClassLoader загрузчику классов, не относящемуся к приложению, до запуска таймера, а затем сбросив его после.
- AccessControlContext вызывающего потока (как задокументировано AccessController ). Если поток запускается из-за вызова API из приложения, тогда ProtectionDomain приложения будет находиться в AccessControlContext , а ProtectionDomain класса приложения будет включать ссылку на его ClassLoader . Этого можно избежать, создав поток с помощью doPrivileged . Обратите внимание, что необходимо позаботиться о том, чтобы использование doPrivileged не позволяло непривилегированным приложениям создавать потоки.
Например:
01020304050607080910111213141516// doPrivileged fixes the AccessControlContext leak, and it is also required
// for calls to Thread.get/setContextClassLoader.
Timer timer = AccessController.doPrivileged(
new
PrivilegedAction() {
public
void
run() {
Thread thread = Thread.currentThread();
ClassLoader savedCL = thread.getContextClassLoader();
thread.setContextClassLoader(
null
);
try
{
// The Timer constructor will create a Thread, which will copy the
// context class loader from the current thread, which is now null.
return
new
Timer(
true
);
}
finally
{
thread.setContextClassLoader(savedCL);
}
}
});
- Связать данные с текущим загрузчиком класса контекста. Обычно это делается с помощью Map < значение classloader > </ classloader . Эта карта должна либо:
- Иметь явные API жизненного цикла. В этом случае должен быть вызван API жизненного цикла. Если код был введен клиентом, то клиент отвечает за добавление слушателя JMX. Если код был введен предварительным требованием WAS, то владелец должен использовать API прослушивателя приложений WAS. Если код принадлежит JDK, команда времени выполнения примет на себя ответственность за его вызов (например, ResourceBundle.clearCache и Introspector.flushCaches ).
- Будьте WeakHashMap, чтобы позволить ключу ClassLoader быть сборщиком мусора. Обратите внимание, что значение не должно содержать неслабую ссылку на классы или объекты, созданные из этого ClassLoader , иначе запись никогда не будет удалена. Либо WeakHashMap <ClassLoader, WeakReference <Class >>, либо WeakHashMap <ClassLoader, Tuple>, где Tuple содержит WeakReference <Class> и WeakReference <Object> для объекта, созданного из класса. В обоих случаях класс удерживается слабо, что по-прежнему позволяет собирать ClassLoader. В последнем случае ссылка на созданный объект будет очищена, если произойдет GC, но предполагается, что он может быть дешево восстановлен.
Эти советы любезно предоставлены гуру WAS Бретт Кейл
Справка: предотвращение утечек памяти в WebSphere Classloader от нашего партнера по JCG