Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java EE (J2EE) и Spring > Tomcat: утечка памяти в Heap Space


Автор: Stampede 10.12.2008, 00:20
Имеется такой неприятный глючок: вебсайт на Томкате периодически вычерпывает всю память, начинает выкидывать OutOfMemoryError, а потом и вовсе становится колом. Приходится перезапукскать.

Самое смешное, что когда я нагружаю вебсайт "нагрузчиком", он прекрасно справляется и держит нагрузку как стойкий оловянный солдатик. У меня даже JCrawler сломался на пятой тысяче запросов, а сайту хоть бы хны. Между тем в реале сайт зависает пару раз в день, причем от рестарта до зависания происходит всего лишь пара сотен обращений к серверу (к динамической его части, где генерится контент).

С учетом всего сказанного, у меня возникло предположение, что дело, скорее всего, в какой-то побочной функциональности: может, в статике, может, браузерный mp3-плейер не отпускает открытый файл, а может где-то треды бегают и держат свои ресурсы.

Вопрос: как вычислить виновника, учитывая, что дело происходит на уделенном сервере, доступ к которому только через терминал (SSH) и FTP.

ЗЫ. Профайлером на локальном компе смотрел, никакой утечки не обнаружил.

Автор: necromancer 10.12.2008, 12:01
Может много памяти кушают пользовательские сессии?
Ты когда нагружаешь нагрузкой пересоздаешь сессии или конектишься к одной и той же.

Автор: Maksym 10.12.2008, 12:14
Stampede
Можно сделать memory dump, забрать его и посмотреть HeapAnalizer'ом.

Добавлено через 40 секунд
правильное название HeapAnalyzer

Автор: Shurr 11.12.2008, 11:33
Stampede
А в какой области памяти падает OutOfMemoryError? Полный стектрейс не покажешь?

Автор: Stampede 11.12.2008, 12:04
necromancer, Maksym, Shurr - спасибо за участие. Борьба с мемори ликом продолжается. Возможно, есть некоторые подвижки, но полной уверенности пока нет.

Цитата(necromancer @  10.12.2008,  03:01 Найти цитируемый пост)
Может много памяти кушают пользовательские сессии?
Ты когда нагружаешь нагрузкой пересоздаешь сессии или конектишься к одной и той же.


Очень хороший вопрос. Но ответ гораздо проще: я вообще без надобности не создаю сессии. И лишь когда посетитель что-то постит, в этот момент сессия и создается. Так что на застревание данных в сессиях я не грешу.

Цитата(Maksym @  10.12.2008,  03:14 Найти цитируемый пост)
Можно сделать memory dump, забрать его и посмотреть HeapAnalyzer'ом.


Именно так и делаю. За прогу спасибо, не знал. Пока ничего вразумительного откопать не удалось. Но наметились кое-какие подозреваемые. Пытаюсь вырубать их одного за другим. По ходу открываю для себя интересные вещи (например, про commons-logging, который зашит в Томкате). Посмотрим, что получится.

Цитата(Shurr @  11.12.2008,  02:33 Найти цитируемый пост)
А в какой области памяти падает OutOfMemoryError? Полный стектрейс не покажешь? 


Да в общем практически всегда в одном и том же месте - на наиболее памятеемкой операции, формировании выходного текста путем слияния контекста с шаблоном. Но проблема не в самой этой операции, а в том, что какой-то гад к этому моменту заныкал себе большой кусок памяти и никак не хочет отдавать обратно.

В общем, продолжение следует.

Автор: Shurr 11.12.2008, 12:32
Stampede
Попробуй включить лог сборки мусора, возможно он поможет выявить утечку.

Автор: Maksym 11.12.2008, 14:07
Stampede
Цитата

Пока ничего вразумительного откопать не удалось. Но наметились кое-какие подозреваемые. 

Там должно быть довольно четко видно кто сколько схавал..
Цитата

Да в общем практически всегда в одном и том же месте - на наиболее памятеемкой операции, формировании выходного текста путем слияния контекста с шаблоном. Но проблема не в самой этой операции, а в том, что какой-то гад к этому моменту заныкал себе большой кусок памяти и никак не хочет отдавать обратно.

А Velocity нигде http://www.nabble.com/out-of-memory--heap-overflow-after-many-VelocityEngin.init-calls-td13514959.html такими вещами. 

Автор: Stampede 18.12.2008, 11:06
Все, эпопея завершена. Глюк пофиксен. Подробности - http://real-english.ru/blog/memory-leak-fixed.htm.

ЗЫ: Maksym получает плюса за подталкивание изучения вопроса в правильном направлении. necromancer - за привлечение внимания к интересному аспекту нагрузочного тестирования.

ЗЗЫ: Всем остальным - спасибо за внимание!

Автор: Kangaroo 18.12.2008, 11:12
Цитата

В свете вновь открывшегося знания пофиксить проблему заняло ровно одну строчку программы.

А можно посмотреть на эту выстраданную строчку? smile

Автор: Asal 18.12.2008, 11:19
Цитата(Stampede @  18.12.2008,  11:06 Найти цитируемый пост)
Все, эпопея завершена

Поздравляю !  smile 

Вот только вопрос:
поделиться той строчкой кода, которая все решила, можешь ?
И где можно глянуть на Breeze, интересно стало smile

Автор: Maksym 18.12.2008, 15:23
Stampede
Memory Analysis - вещь.

Автор: Asal 18.12.2008, 15:30
Цитата(Maksym @  18.12.2008,  15:23 Найти цитируемый пост)
Memory Analysis - вещь.

у меня эта вещь не запустилась, при 2 гигах оперативы, ругнулась: "Не могу выделить нужное количество памяти"

Автор: COVD 18.12.2008, 23:41

Цитата

Глюк пофиксен. Подробности - здесь.
 

И чей же это "глюк". Неужели Томката?

Цитата

В две секунды стало ясно, что треды Томката не отпускают переменную типа ThreadLocal

Автор: Kangaroo 18.12.2008, 23:55
Цитата

Stampede (Online)

Есть надежда, что сейчас мы получим долгожданный ответ smile

Автор: Stampede 19.12.2008, 00:46
Цитата(Kangaroo @  18.12.2008,  14:55 Найти цитируемый пост)
Есть надежда, что сейчас мы получим долгожданный ответ smile 


Есть такая буква - http://real-english.ru/blog/memory-leak-fixed.htm smile

Добавлено: куча подробностей, скриншоты, сниппеты, раздел "благодарности". Там же ответы на все вопросы, которые поднимались в обсуждении.

Цитата(Asal @  18.12.2008,  02:19 Найти цитируемый пост)
И где можно глянуть на Breeze, интересно стало smile 


Давай мыло, что-нибудь придумаем.

Цитата(COVD @  18.12.2008,  14:41 Найти цитируемый пост)
И чей же это "глюк". Неужели Томката?


Вполне мог быть и Томката. Такое в истории http://www.google.com/search?q=tomcat+memory+leak. И потом, я разве что-то говорил, что грешу именно на него? Упоминание Томката в заголовке имело целью всего лишь обозначить место действия. А если язвительное "чей же это "глюк"" - это все, что ты вынес из этой истории, то даже не знаю, что и сказать.

Всем остальным - еще раз спасибо за участие!

Автор: COVD 19.12.2008, 06:46
Цитата

А если язвительное...


 smile я неловко выразился. Уточнял, баг это или фича, что "треды ... не отпускают..". Но, наверное, диагноз не очевиден. 

Автор: Stampede 19.12.2008, 08:10
Цитата(COVD @  18.12.2008,  21:46 Найти цитируемый пост)
Уточнял, баг это или фича, что "треды ... не отпускают.."


Ах, вот оно как оказывается smile

Знаешь, COVD, этот случай подтвердил мою давнишнюю теорию, что большинство конфликтов порождаются элементарным недопониманием, кто чего имел в виду. Ну хорошо, я рад, что все разрешилось без мордобития и эскалации напряженности. Каковой факт я отчасти ставлю в заслугу самому себе: много слов крутилось у меня на языке, но я выбрал самые нейтральные smile

Теперь по сути. Томкат в данном случае действительно ни при чем. Это чисто мой программистский недосмотр: необнуление переменной в момент, когда она перестает быть нужна. Но и большого криминала там с моей стороны тоже нет: поскольку ThreadLocal данного типа привязан к потоку под фиксированным именем, накопления связанной пямяти через этот механизм не происходит - при каждом новом запросе вместо старой инфы прописывается новая, а старая освобждается для сбора мусорщиком. Именно поэтому я не только не мог отловить это дело профайлером, но мне даже не удавалось завалить сервер нагрузчиком!

Резюмируя историю в целом: от ошибок не застрахован никто. Как известно, не ошибается тот, кто ничего не делает. Важно не бояться признаться в своих ошибках, быть готовым ликвидировать их последствия, и извлекать уроки на будущее. Я могу сказать, что я вышел из этой ситуации более сильным, чем когда в нее входил. Я получил навыки борьбы с ликами, пополнил свой арсенал полезными прогами, подчистил многие места в конфигах, обновил кучу библиотек. Если я в будущем снова столкнусь с проблемами утечки памяти, я буду готов к борьбе с ними гораздо лучше. Так что трепещите, мемори лики - я вас больше не боюсь!

Ну и надеюсь, мой опыт окажется в чем-то полезным и для вас. Не всем же учиться строго на собственных ошибках smile

Автор: ivg 19.12.2008, 11:11
Позволю себе сделать небольшое дополнение по теме. По моему скромному разумению лучше делать так:
Код

    public class BreezeController implements Controller {
        ...
        public void service(HttpServletRequest req, HttpServletResponse resp, ServletContext sctx) {
            ...
            ThreadContext tc = Application.getThreadContext();
            RequestContext oldContext = tc.get(RequestContext.DEFAULT_NAME);
            tc.put(RequestContext.DEFAULT_NAME, new RequestContext(sctx, req, resp));
            try {
                ...
            } finally {
                ...
                tc.put(RequestContext.DEFAULT_NAME, oldContext);
            }
        }
    }

Ну то есть идея понятна - не обнулять/удалять контекст из ThreadLocal, а возвращать старое значение перед возвратом управления контейнеру.
Это нужно во избежании потери нашего контекста, например при использовании RequestDispatcher.

Автор: Stampede 19.12.2008, 11:55
Цитата(ivg @  19.12.2008,  02:11 Найти цитируемый пост)
Это нужно во избежании потери нашего контекста, например при использовании RequestDispatcher.


На первый взгляд выглядит разумно: действительно, ведь могут существовать компоненты, которые сидят выше в цепочке вызовов и при этом используют конструкции фреймворка - например, какие-нибудь фильтры. Однако если продолжать размышлять в этом направлении, то вместо того, чтобы сохранять потенциально возможный предыдущий контекст таким "стековым" способом, возможно, лучше было бы дать компонентам из разных слоев возможность обмениваться инфой друг с другом - например, через механизм вложенных контекстов или что-то типа того. В общем, надо подумать.

А вообще в архитектуре Бриза контроллер - это фактически точка входа во фреймворк (как он видится со стороны запроса), и RequestDispatcher отрабатывает уже внутри него, так что до сих пор проблем с потерей контекста не возникало.

Автор: necromancer 19.12.2008, 15:12
to Stampede: а вы не хотели бы выложить исходники велкикого и ужастного breeze?

Автор: ivg 19.12.2008, 20:16
Цитата(Stampede @  19.12.2008,  14:55 Найти цитируемый пост)
контроллер - это фактически точка входа во фреймворк (как он видится со стороны запроса), и RequestDispatcher отрабатывает уже внутри него

На всякий случай запощу пример потери контекста, о которой я говорил:
Код

package test;

import java.io.IOException;
import java.io.Writer;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class TestServlet extends HttpServlet {

    private static final String REQUEST_DEEP_NAME = "REQUEST_DEEP";
    private static final ThreadLocal<String> CONTEXT = new ThreadLocal<String>();
    
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        
        CONTEXT.set("Data");
        try {
            this.doService(req, resp);
        } finally {
            CONTEXT.remove();
        }
    }
    
    private void doService(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
        
        Integer deep = (Integer)req.getAttribute(REQUEST_DEEP_NAME);
        Writer w = resp.getWriter();
        if (deep == null) {
            req.setAttribute(REQUEST_DEEP_NAME, Integer.valueOf(1));
            resp.setContentType("text/plain");
            w.write("-- begin " + CONTEXT.get() + " --\n");
            req.getRequestDispatcher("/").include(req, resp);
            w.write("-- end " + CONTEXT.get() + " --\n");
        } else {
            w.write("< body >\n");
        }
    }
}


web.xml:
Код

<web-app ...>
    ...
    <servlet>
        <display-name>TestServlet</display-name>
        <servlet-name>TestServlet</servlet-name>
        <servlet-class>test.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>TestServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)