Случай и анализ
Проблема фон
В Tomcat следующий код находится внутри веб -приложения, который приведет к утечке WebappClassLoader и не может быть переработана.
открытый класс mycounter {private int count = 0; public void Increment () {count ++; } public int getCount () {return count; }} открытый класс mythreadlocal extends Threadlocal <Mycounter> {} открытый класс утечка extends httpservlet {private static mythreadlocal mythreadlocal = new mithreadlocal (); Защищенная void Doget (httpservletrequest, httpservletresponse response) бросает ServletException, ioException {mycounter = mythreadlocal.get (); if (счетчик == null) {counter = new mycounter (); mythreadlocal.set (счетчик); } response.getWriter (). println («Текущий поток обслуживал этот сервлет» + counter.getCount () + «times»); counter.increment (); }} В приведенном выше коде, пока LeakingServlet называется один раз, а поток, выполняющий его, не останавливается, возникнет утечка WebappClassLoader . Каждый раз, когда вы reload приложение, у вас будет дополнительный экземпляр WebappClassLoader , который в конечном итоге приведет к PermGen OutOfMemoryException .
Решить проблему
Теперь давайте подумаем об этом: почему ThreadLocal подкласс выше вызывает утечки памяти?
WebAppClassLoader
Прежде всего, нам нужно выяснить, что, черт возьми, является WebappClassLoader ?
Для веб -приложений, работающих в контейнерах Java EE, реализация классных погрузчиков отличается от реализации приложений общих Java. Различные веб -контейнеры также будут реализованы по -разному. В Apache Tomcat каждое веб -приложение имеет соответствующий экземпляр загрузчика класса. Этот загрузчик класса также использует прокси -режим, разница в том, что он сначала пытается загрузить определенный класс, а затем прокси в родительский загрузчик класса, если его не найден. Это противоположность порядку общих погрузчиков класса. Это рекомендуемая практика в спецификации сервлета Java, и его цель - сделать приоритеты собственных классов веб -приложения выше, чем те, которые предоставляются в веб -контейнере. Исключением из этого прокси -шаблона является то, что классы в основной библиотеке Java не находятся в рамках поиска. Это также для обеспечения безопасности типа библиотеки ядра Java.
Другими словами, WebappClassLoader - это пользовательский загрузчик класса для загрузки веб -приложений. Загрузчик класса каждого веб -приложения отличается. Это должно изолировать классы, загруженные различными приложениями.
Так какое отношение к утечкам памяти связана функции WebappClassLoader ? Это еще не видно, но это очень важная функция, которая заслуживает нашего внимания: у каждого веб -приложения есть свой собственный WebappClassLoader , который отличается от загрузчика класса Java Core.
Мы знаем, что утечка WebappClassLoader должна быть потому, что на нее тесно ссылаются другие объекты, поэтому мы можем попытаться нарисовать их диаграмму ссылок. и т. д! Что именно работает загрузчик класса? Почему это вынуждено цитировать?
Жизненный цикл класса и загрузчик класса
Чтобы решить вышеуказанную проблему, мы должны изучить взаимосвязь между жизненным циклом класса и загрузчиком класса.
Главное, что связано с нашим делом, - это удаление класса:
После использования класса, если будет выполнена следующая ситуация, класс будет удален:
1. Все экземпляры этого класса были переработаны, то есть никаких случаев этого класса в куче Java.
2. Загрузка ClassLoader , загружающий этот класс, был переработан.
3. Объект java.lang.Class , соответствующий этому классу, нигде не ссылается, и нет никакого метода для доступа к классу через рефлексию в любом месте.
Если все вышеперечисленные три условия будут выполнены, JVM удалит класс во время сбора мусора в области метода. Процесс удаления класса фактически заключается в том, чтобы очистить информацию класса в области метода, а весь жизненный цикл класса Java заканчивается.
Классы, загруженные загрузчиком класса, который поставляется с виртуальной машиной Java, никогда не будут удалены в течение жизненного цикла виртуальной машины. Классовые погрузчики, которые поставляются с виртуальными машинами Java, включают погрузчики класса корневых классов, погрузчики класса расширения и загрузки системы. Сама виртуальная машина Java всегда ссылается на эти загрузчики классов, и эти загрузки классов всегда относятся к объектам класса класса, которые они загружают, поэтому эти объекты класса всегда доступны.
Классы, загруженные пользовательскими загрузчиками класса, могут быть разгружены.
Обратите внимание на вышеуказанное предложение. Если WebappClassLoader утечка, это означает, что классы, которые он загружает, не могут быть удалены, что объясняет, почему вышеупомянутый код вызывает PermGen OutOfMemoryException .
Посмотрите на картинку ниже в ключевой точке
Мы можем обнаружить, что объект загрузчика класса двунаправленно связан с объектом класса, который он загружает. Это означает, что объект класса может быть виновником принудительной ссылки на WebappClassLoader , заставляя его протекать.
Цитата диаграмма отношений
Понимая взаимосвязь между загрузчиком класса и жизненным циклом класса, мы можем начать рисовать диаграмму эталонных отношений. ( LeakingServlet.class и myThreadLocal на рисунке не являются строгими на эталонном рисунке, в основном, чтобы выразить, что myThreadLocal является переменной класса)
Ниже мы анализируем причину утечки WebappClassLoader на основе вышеуказанного рисунка.
1. LeakingServlet содержит static MyThreadLocal , что приводит к жизненному циклу myThreadLocal до тех пор, пока жизненный цикл класса LeakingServlet . Это означает, что myThreadLocal не будет переработана, а слабые ссылки бесполезны, поэтому текущий поток не может очистить сильную ссылку счетчика через меры защиты ThreadLocalMap .
2. Сильная эталонная цепочка: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , что приводит к утечке WebappClassLoader .
Суммировать
Утечки памяти трудно обнаружить и часто вызваны многими причинами. Threadlocal стал частым посетителем утечек памяти из -за его жизненного цикла, связанного с нитями, и небольшая небрежность приведет к катастрофе. Эта статья является просто анализом конкретного случая. Если вы можете использовать это, чтобы применить его к другим случаям, это было бы превосходно. Надеюсь, эта статья будет полезна всем.