Image

Imagedimmik wrote in Imageru_java

Categories:

"Утечка памяти" в tomcat - может кому полезно будет

Итак, в середине прошлого года у меня возникла проблема.
Достаточно масштабное j2ee-web приложение нормально работало в dev-версии, но при выставлении наружу через каждые примерно 20000 запросов падало из-за нехватки памяти.
UPD: Да, забыл сказать - отображение все на технологии jsp.

Система уходила в swap, сервер начинал тормозить, в итоге выдавалась java.lang.OutOfMemoryError и tomcat приходилось перегружать.
Это происходило 2-4 раза в день.
Что интересно, не было прямой зависимости от количества запросов - сервер мог держать и 50000, а мог упасть и после 20000 запросов.
В среднем отжиралось порядка 40 килобайт на запрос.
В течение достаточно долгого времени я исследовал свой код на предмет утечек памяти.
Нашел несколько незначительных глюков, убрал их, но это не помогло.

В итоге пришлось воспользоваться профайлером на рабочей версии системы (до этого я не решался этого делать, так как система обслуживает много клиентов и простой в течение 20-40 минут, на время снятия снепшота был гораздо хуже чем перегрузка 4 раза в день, на локальной же версии профайлер никаких утечек не показывал).


А оказалось, что проблема в следующем:
На основное количество сожранной памяти были ссылки из org.apache.jasper.runtime.PageContextImpl, которые, в свою очередь, хранились в org.apache.jasper.runtime.JspFactoryImpl.pool.
При обработки страницы PageContextImpl заносились в этот самый пул и жили там весьма долго.
Одна из технических особенностей нашей системы в том, что весьма большие куски данных заносились в переменные в контексте страницы (scope="page") и затем уже выводились.
Примерно так:
<c:set var="a">[...кусок страницы...]</c:set>
${a}

Таким образом переменные в которых, повторюсь, хранились весьма объемные куски данных, не очищались сборщиком мусора, а жили долго и счастливо, до следующей перегрузки сервера.
Такое поведение, с моей точки зрения, весьма нелогично. Если рассматривать области действия переменных в порядке убывания "перманентности", то дольше всего переменные живут в application, потов в сессии, потом в request, а уж переменные в странице - это вообще совсем временные...
Еще эти же самые куски могли бы храниться в пуле тегов (если tagPooling включен), но эту опцию для генерации jsp я отключил когда проблемы только начались, что само по себе помогло несильно.
В итоге мне пришлось в исходниках томкэта в классе org.apache.jasper.runtime.JspFactoryImpl переменную USE_POOL выставить в false.
В оригинальных исходниках она равна true и никаким образом снаружи не изменяется.
Я перекомпилил исходники и выложил все .jar, относящееся к jasper в рабочую версию томкета.

После этого проблема исчезла - после очистки памяти каждый раз остается не больше 300 мегабайт занятой памяти, что вполне приемлемо.

В новой версии tomcat (начиная с Tomcat 5.5.15 ввели возможность регулировать использование пула для контекста страницы, org.apache.jasper.runtime.JspFactoryImpl.USE_POOL можно выставить в false с помощью переменной окружения.

Кстати, насчет пула тегов - его тоже ОБЯЗАТЕЛЬНО надо дизаблить. На другом сервере jasper был пропатченный, но уже скомпиленные .jsp использовали кеш для тагов. Пока они не были полностью перекомпилены проблема оставалась.

Надеюсь, кому-нибудь будет полезно - решение по форумам и пр. я искал в течение где-то двух месяцев и не нашел ничего по этому поводу. ;)

P.S. На самом деле, насколько я понимаю, это не является утечкой памяти как таковой.
Думаю, что на уровне порядка 2-3 гб количество занятой памяти перестало бы расти, но у меня нету этих 2-3 гб. :)