Как бороться с OutOfMemoryError на практике, или ох уж мне эти базы...

Как бороться с OutOfMemoryError на практике, или ох уж мне эти базы данных

417
ПОДЕЛИТЬСЯ

Приветствую, Хабр!
Незначительно лирикиСегодня, 2015-03-21, я решил сделать пол-дела, и всё-таки начать писать статью о том, как же всё-таки начать осознавать, что же делать с OOM , да и вообщем научиться ковырять heap-dump’ы (буду именовать их просто дампами, для простоты речи. Также я постараюсь избегать англицизмов, где это может быть).
Загаданный мной размер «работ» по написанию данной статьи кажется мне не однодневным, а почему статья обязана будет показаться только через пару недель спустя день.
Исследуются такие инструменты, как JVisualVM (рассмотрю некие плагины к нему и OQL Console), Eclipse Memory Analyzing Tool. В данной статье я постараюсь разжевать, что делать с дампами в Java, как осознать причину либо приблизиться к причине появления OOM, поглядеть на инструменты для анализа дампов, инструмент (один, да) для мониторинга хипа, и вообщем вникнуть в это дело для общего развития.
Чрезвычайно много понаписал, но надеюсь, что всё лишь по делу 🙂

Предыстория
Кому-то это может быть ещё непонятно. Для начала необходимо осознать, как возникает OOM.
Пусть это будет гб ОЗУ. Представьте для себя, что есть какой-то верхний предел занимаемой оперативки для приложения.
Само по для себя появление OOM в каком-то из потоков ещё не значит, что конкретно этот поток «выжрал» всю вольную память, да и вообщем не значит, что конкретно тот кусочек кода, который привёл к OOM, виноват в этом.
В этом случае как раз и возникает OOM, не связанный с источником трудности, когда стектрейс покажет совершенно не того виновника падения приложения. Полностью нормальна ситуация, когда какой-то поток чем-то занимался, поедая память, «дозанимался» сиим до состояния «ещё незначительно, и я лопну», и завершил выполнение, приостановившись. А в это время какой-то иной поток решил запросить для собственной малеханькой работы ещё незначительно памяти, сборщик мусора попыжылся, естественно, но мусора уже в памяти не отыскал.

В общей трудности пара недель времени, которые растянулись на полтора месяца, ведь занимался я не лишь этими неуввязками. Около недельки я изучил, как сделать лучше жизнь парочки наших приложений, чтоб они закончили себя неустойчиво вести. И ещё неделю-две издержал на то, чтоб привести их в порядок. Есть и иной вариант.
Из отысканного: посторонняя библиотека, и, естественно же, некие неучтённые вещи в вызовах хранимых процедур.
В одном приложении симптомы были последующие: в зависимости от перегрузки на сервис, оно могло свалиться через день, а могло через двое. Раз помониторить состояние памяти, то было видно, что приложение равномерно набирало «размер», и в определённый момент просто ложилось.
А опосля обновления версии, когда была изменена и версия Tomcat с 7й до 8й, и JRE, оно вдруг в одну из пятниц (проработав вменяемо до этого ни много ни не достаточно — 2 недельки) начало творить такие вещи, что постыдно признаваться в этом. 🙂 Оно может вести себя отлично долгий срок, а могло закончить отвечать минут через 10 опосля перезагрузки, либо вдруг в один момент свалиться, сожрав всю вольную память (это я уже на данный момент вижу, следя за ним). С иным приложением несколько увлекательнее.

В обоих историях чрезвычайно полезны оказались дампы, благодаря им удалось найти все предпосылки падений, подружившись с таковыми инструментами, как JVisualVM (буду именовать его JVVM), Eclipse Memory Analyzing Tool (MAT) и языком OQL (может быть я не умею его верно готовить в MAT, но мне оказалось легче сдружиться с реализацией OQL конкретно в JVVM).
Ещё для вас пригодится вольная оперативка для того, чтоб было куда загружать дампы. Её размер должен быть соизмерим с размером открываемого дампа.

Начало
Итак, начну потихоньку открывать карты, и начну конкретно с JVVM.

Этот инструмент в соединении с jstatd и jmx дозволяет удалённо следить за жизнью приложения на сервере: Heap, процессор, PermGen, количество потоков и классов, активность потоков, дозволяет проводить профилирование.
Также JVVM расширяем, и я не преминул пользоваться данной возможностью, установив некие плагины, которые дозволили куда больше вещей, к примеру, смотреть и взаимодействать с MBean’ами, следить за деталями хипа, вести долгое наблюдение за приложением, держа в «голове» куда больший период метрик, чем предоставляемый вкладкой Monitor час.

Вот так смотрится набор установленных плагинов.
Visual GC (VGC) дозволяет созидать метрики, связанные с хипом.
Детальнее о том, из чего же состоит хип в данной нашей Java

Вот два скриншота вкладки VGC, которые демонстрируют, как ведут себя два различных приложения.
Слева Вы сможете узреть такие разделы хипа, как Perm Gen, Old Gen, Survivor 0, Survivor 1, и Eden Space.
Все эти составляющие — участки в оперативке, в которую и складываются объекты.
PermGen — Permanent Generation — область памяти в JVM, предназначенная для хранения описания классов Java и неких доп данных.
Old Gen — это область памяти для довольно старенькых объектов, которые пережили несколько перекладываний с места на место в Survivor-областях, и в момент какого-то еще одного переливания попадают в область «старых» объектов.
Survivor 0 и 1 — это области, в которые попадают объекты, которые опосля сотворения объекта в Eden Space пережили его очистку, то есть не стали мусором на момент, когда Eden Space начал чиститься Garbage Collector’ом (GC). При каждом запуске очистки Eden Space объекты из активного в текущий момент Survivor’а перекладываются в пассивный, плюс добавляются новейшие, и опосля этого Survivor’ы изменяются статусами, пассивный становится активным, а активный — пассивным.
Eden Space — область памяти, в которой новейшие объекты порождаются. При нехватке памяти в данной области запускается цикл GC.

Любая из этих областей может быть отрегулирована по размеру в процессе работы приложения самой виртуальной машинкой.
Виртуальная машинка поначалу постарается держать себя «в узде». Раз вы указываете -Xmx в 2 гб, к примеру, то это не значит, что все 2 гб будут сходу же заняты (раз не запускать сходу что-то активно кушающее память, естественно же).
Приложение проработало больше 90 часов, и в принципе JVM считает, что приложению требуется не так уж и много, около 540 МБ. На 3-ем скриншоте видно неактивную стадию приложения, которое не употребляется на выходных — Eden растёт умеренно, Survivor’ы перекладываются через равные промежутки времени, Old фактически не растёт.

Бывают пиковые ситуации, когда виртуальная машинка даже выделяет под хип еще больше памяти, но я думаю, что это какие-то ещё «неучтёнки», о которых я расскажу детальнее ниже по тексту, а может просто виртуальная машинка выделила больше памяти под Eden, к примеру, чтоб объекты в нём успевали стать мусором до последующего цикла чистки.

Участки, которые на последующем скриншоте я обозначил красноватым — это как раз возрастание Old, когда некие объекты не успевают стать мусором, чтоб быть удалёнными из памяти ранее, и всё-таки попадают в Old. На протяжении бардовых участков можно созидать гребёнку — это Eden так себя ведёт. Голубий участок — исключение.

На протяжении голубого участка быстрее всего виртуальная машинка решила, что необходимо прирастить размер Eden-области, поэтому как при увеличении масштаба в Tracer’е видно, что GC закончил «частить» и таковых маленьких колебаний, как ранее, сейчас нет, колебания стали медленными и редкими.

Перейдём ко второму приложению:

В нём Eden припоминает мне какой-то уровень из Mortal Kombat, арену с шипами. Была таковая, кажется… А График GC — шипы из NFS Hot Pursuit, вот те вот, плоские ещё.
Числа справа от заглавий областей указывают:
Всего он может вырости до 546 мб. 1) что Eden имеет размер в 50 мб, и то, что нарисовано в конце графика, крайнее из значений на текущий момент — занято 25 мб.
2) что Old может вырасти до 1,333 гига, на данный момент занимает 405 МБ, и забит на 145,5 МБ.
Так же для Survivor-областей и Perm Gen.
К примеру, что активная фаза у этого приложения — с 8:30 до 17:30 в рабочие дни, и что даже на выходных оно тоже работает 🙂 Для сопоставления — вот Для вас Tracer-график за 75 часов работы второго приложения, думаю, кое-какие выводы вы можете сделать из него.

Раз вы вдруг узрели в собственном приложении, что Old-область заполнена — попытайтесь просто подождать, когда она переполнится, быстрее всего она заполнена уже мусором.

Мусор — это объекты, на которые нет активных ссылок из остальных объектов, либо целые комплексы таковых объектов (к примеру, какое-то «облако» взаимосвязанных оъектов может стать мусором, раз набор ссылок показывает лишь на объекты снутри этого «облака», и ни на один объект в этом «облаке» ничто не ссылается «снаружи»).

Это был лаконичный пересказ того, что я вызнал про структуру хипа за время, пока гуглил.
Предпосылки
Итак, случилось сходу две вещи:
1) опосля перехода на наиболее новейшие библиотеки/томкеты/джавы в одну из пятниц приложение, которое я уже длительное время веду, вдруг стало вести себя из рук вон плохо спустя две недельки опосля выставления.
2) мне на рефакторинг дали проект, который тоже вёл себя до некого времени не чрезвычайно отлично.

Предупреждаю, что какие-то детали я мог уже забыть. Я уже не помню, в каком точно порядке произошли эти действия, но опосля «чёрной пятницы» я решил в конце концов-то разобраться с дампами памяти детальнее, чтоб это наиболее не было для меня чёрным ящиком.

По первому случаю симптомы были такие: все потоки, отвественные за обработку запросов, выжраны, на базу данных открыто всего 11 коннектов, и те не огласить, что употребляются, база говорила, что они в состоянии recv sleep, то есть ждут, когда же их начнут применять.
Опосля перезагрузки приложение оживало, но прожить могло недолго, вечерком той же пятницы жило подольше всего, но уже опосля окончания рабочего дня таки опять упало. Картина постоянно была схожей: 11 коннектов к базе, и только один, вроде бы, что-то делает.
Огласить, что OOM привёл меня к поиску обстоятельств, не могу, но приобретенные познания при поиске обстоятельств дозволили начать активную борьбу с OOM. Память, кстати, была на минимуме.

Когда я открыл хип в JVVM, из него было трудно что-или осознать.

Подсознание давало подсказку, что причина где-то в работе с базой.
Поиск посреди классов произнес мне, что в памяти аж 29 DataSource, хотя обязано быть всего 7.

Это и отдало мне точку, от которой можно было бы оттолкнуться, начать распутывать клубок.

OQL
Посиживать переклацывать в просмотровщике все эти объекты было некогда, и моё внимание в конце концов-то завлекла вкладка OQL Console, я поразмыслил, что вот он, момент истины — я либо начну применять её на полную катушку, либо так и забью на всё это.

До этого, чем начать, естественно же был задан вопросец гуглу, и он любезно предоставил шпаргалку (cheat sheet) по использованию OQL в JVVM: http://visualvm.java.net/oqlhelp.html

Поначалу богатство сжатой инфы привело меня в угнетение, но опосля внедрения google-фу на свет таки возник вот таковой OQL-запрос:
select {instance: x, uri: x.url.toString(), connPool: x.connectionPool}
from org.apache.tomcat.dbcp.dbcp2.BasicDataSource x
where x.url != null
&& x.url.toString() == "jdbc:sybase:Tds:ip-адрес:порт/базаДанных"
Это уже исправленная и дополненная, финальная версия этого запроса 🙂
Итог можно узреть на скриншоте:

Опосля нажатия на BasicDataSource#7 мы попадаем на подходящий объект во вкладке Instances:

Через некое время до меня дошло, что есть одно несхождение с конфигурацией, указанной в теге Resource в томкете, в файле /conf/context.xml. Ведь в дампе параметр maxTotal имеет значение 8, в то время, как мы указывали maxActive равным 20…

Здесь-то до меня и начало доходить, что приложение жило с неверной конфигурацией пула коннектов все эти две недельки!
Для краткости напишу здесь, что в случае, раз вы используете Tomcat и в качестве пула коннектов — DBCP, то в 7м томкете употребляется DBCP версии 1.4, а в 8м томкете — уже DBCP 2.0, в котором, как я позже узнал, решили переименовать некие характеристики! А про maxTotal вообщем на главной страничке веб-сайта написано 🙂
http://commons.apache.org/proper/commons-dbcp/
«Users should also be aware that some configuration options (e.g. maxActive to maxTotal) have been renamed to align them with the new names used by Commons Pool 2.»

Предпосылки
Обозвал их по всякому, успокоился, и решил разобраться.
Как оказалось, класс BasicDataSourceFactory просто напросто получает этот самый Resource, глядит, есть ли нужные ему характеристики, и конфискует их в порождаемый объект BasicDataSource, молча игнорируя напрочь всё, что его не интересует.
Так и вышло, что они переименовали самые радостные характеристики, maxActive => maxTotal, maxWait => maxWaitMillis, removeAbandoned => removeAbandonedOnBorrow & removeAbandonedOnMaintenance.
По умолчанию maxTotal, как и ранее, равен 8; removeAbandonedOnBorrow, removeAbandonedOnMaintenance = false, maxWaitMillis устанавливается в значение «ждать вечно».
Вышло, что пул оказался сконфигурирован с наименьшим количеством коннектов; в случае, раз заканчиваются вольные коннекты — приложение молча ожидает, когда они освободятся; и добивает всё молчанка в логах по поводу «заброшенных» коннектов — то, что могло бы сходу показать, в каком конкретно месте программер мудак код хватает коннект, но не дает его обратно по окончанию собственной работы.
Это на данный момент вся мозаика сложилась быстро, а добывались эти познания подольше.

Когда и раз Tomcat 8 обновит версию DBCP до 2.1+, думаю, что администраторам раскроются почти все тайны про их конфигурации Resource 🙂 «Так быть не должно», решил я, и запилил патчик (https://issues.apache.org/jira/browse/DBCP-435, выразился в http://svn.apache.org/viewvc/commons/proper/dbcp/tags/DBCP_2_1/src/main/java/org/apache/commons/dbcp2/BasicDataSourceFactory.java?view=markup ), патч был принят и вошёл в версию DBCP 2.1.

По поводу этого происшествия мне только осталось поведать ещё одну деталь — какого чёрта в дампе было аж 29 DataSource’ов заместо всего 7 штук. Разгадка кроется в очевидной математике, 7*4=28 +1=29.
Детальнее о том, почему нельзя закидывать Resource в файл /conf/context.xml томкетаНа каждую подпапку снутри папки /webapps поднимается своя копия /conf/context.xml, а означает то количество Resource, которые там есть, следует умножать на количество приложений, чтоб получить общее количество пулов, поднятых в памяти томкета. У ResourceLink характеристики name и global могут содержать однообразные значения. Дальше нужно пользоваться тегом ResourceLink, его лучше поместить в приложение, в проекте, вовнутрь файла /META-INF/context.xml — это так именуемый «per-app context», то есть контекст, который содержит объявления компонентов, которые будут доступны лишь для разворачиваемого приложения. На вопросец «что в этом случае делать?» ответ будет таковым: необходимо вынести все объявления Resource из /conf/context.xml в файл /conf/server.xml, вовнутрь тега GlobalNamingResources. Там Вы сможете отыскать один, имеющийся по умолчанию, Resource name=«UserDatabase», вот под ним и располагайте свои пулы.
Для примера:
/>
Эта ссылка будет выхватывать из глобально объявленных ресурсов DataSource с именованием «jdbc/MyDB», и ресурс станет доступен приложению.
ResourceLink можно (но не необходимо) расположить и в /conf/context.xml, но в этом случае доступ к ресурсам, объявленным глобально, будет у всех приложений, пусть даже и не будет столько копий DataSource в памяти.
Ознакомиться с деталями можно вот здесь: GlobalNamingResources — http://tomcat.apache.org/tomcat-7.0-doc/config/globalresources.html#Environment_Entries, ResourceLink — http://tomcat.apache.org/tomcat-7.0-doc/config/globalresources.html#Resource_Links, также можно просмотреть эту страничку: tomcat.apache.org/tomcat-7.0-doc/config/context.html.
Для TC8 эти же странички: http://tomcat.apache.org/tomcat-8.0-doc/config/globalresources.html и http://tomcat.apache.org/tomcat-8.0-doc/config/context.html .
Опосля этого всё стало ясно: 11 коннектов было поэтому, что в одном, активном DataSource было съедено 8 коннектов (maxTotal = 8), и ещё по minIdle=1 в 3-х остальных неиспользуемых DataSource-копиях.

В ту пятницу мы откатились на Tomcat 7, который лежал рядышком, и ожидал, когда от него избавятся, это отдало время расслабленно во всём разобраться.
DBCP отрадно сказал в логфайл catalina.log о том, что Плюс позднее, уже на TC7, нашлась утечка коннектов, всё благодаря removeAbandoned+logAbandoned.
"org.apache.tomcat.dbcp.dbcp.AbandonedTrace$AbandonedObjectException: DBCP object created 2015-02-10 09:34:20 by the following code was never closed:
at org.apache.tomcat.dbcp.dbcp.AbandonedTrace.setStackTrace(AbandonedTrace.java:139)
at org.apache.tomcat.dbcp.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:81)
at org.apache.tomcat.dbcp.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
at org.apache.tomcat.dbcp.dbcp.BasicDataSource.getConnection(BasicDataSource.java:1044)
at наш.пакет.СуперКласс.getConnection(СуперКласс.java:100500)
at наш.пакет.СуперКласс.плохойПлохойМетод(СуперКласс.java:100800)
at наш.пакет.СуперКласс.вполнеВменяемыйМетод2(СуперКласс.java:100700)
at наш.пакет.СуперКласс.вполнеВменяемыйМетод1(СуперКласс.java:100600)
ещё куча строк…"
СуперКласс вызывается изредка, потому на него и не направляли внимания так долго. Вот этот вот плохойПлохойМетод имеет в сигнатуре Connection con, но снутри была конструкция «con = getConnection();», которая и стала камнем преткновения. Плюс к этому, вызовы происходили, я так понимаю, не во время рабочего дня, так что даже раз что-то и подвисало, то никому уже не было дела до этого. А в ТуСамуюПятницу просто звёзды сошлись, начальнику департамента заказчика пригодилось поглядеть кое-что 🙂

Приложение №2
Что же касается «события №2» — мне дали приложение на рефакторинг, и оно на серверах здесь же вздумало свалиться.
Дампы попали уже ко мне, и я решил испытать поковырять и их тоже.
Открыл дамп в JVVM, и «чё-то приуныл»:

Что можно осознать из Object[], да ещё и в таком количестве?
( Опытнейший человек, естественно же, увидел уже причину, правда? 🙂 )

Так я наткнулся на этот вопросец на StackOverflow: http://stackoverflow.com/questions/2064427/recommendations-for-a-heap-analysis-tool-for-java. Так у меня зародилась мысль «ну неуж-то никто ранее не занимался сиим, ведь наверное уже есть готовый инструмент!».
Посмотрев предложенные варианты, я решил тормознуть на MAT, нужно было испытать хоть что-то, а это открытый проект, да ещё и с куда огромным количеством голосов, чем у других пт.

Eclipse Memory Analyzing Tool
Итак, MAT.
Быть может кто-то подскажет в комментах, чего же ему не хватает, но я решил делему, установив MAT в Eclipse. Рекомендую закачивать последнюю версию Eclipse, и устанавливать MAT туда, поэтому как самостоятельная версия MAT ведёт себя плохо, там какая-то чертовщина с диалогами, в их не видно содержимого в полях.

Открыв дамп в MAT я запросил выполнение Leak Suspects Report.

Удивлению не было предела, честно говоря.

1.2 гига весят соединения в базу.

Каждое соединение весит от 17 до 81 мб.

Ну и ещё «немного» сам пул.
Визуализировать делему посодействовал отчёт Dominator Tree:

Предпосылкой всех падений оказались километры SQLWarning’ов, база настойчиво пробовала отдать осознать, что «010SK: Database cannot set connection option SET_READONLY_TRUE.», а пул коннектов BoneCP не вычищает SQLWarning’и опосля освобождения и возврата коннектов в пул (может быть это где-то можно сконфигурировать? Подскажите, раз кто знает).
Google произнес, что таковая неувязка с Sybase ASE известна ещё с 2004 года: https://forum.hibernate.org/viewtopic.php?f=1&t=932731
Раз кратко, то «Sybase ASE doesn’t require any optimizations, therefore setReadOnly() produces a SQLWarning.», и указанные решения всё ещё работают.
Но это не совершенно решение трудности, поэтому как решение трудности — это когда при возврате соединения в пул все уведомления базы очищаются в силу того, что они уже никогда никому не пригодятся.
И DBCP таки умеет делать это: http://svn.apache.org/viewvc/commons/proper/dbcp/tags/DBCP_1_4/src/java/org/apache/commons/dbcp/PoolableConnectionFactory.java?view=markup, способ passivateObject(Object obj), в строке 687 можно узреть conn.clearWarnings();, этот вызов и выручает от км SQLWarning’ов в памяти.
О этом я вызнал из тикета: https://issues.apache.org/jira/browse/DBCP-102
Также мне подсказали про вот таковой тикет в багтрекере: https://issues.apache.org/jira/browse/DBCP-234, но он касается уже версии DBCP 2.0.

И верно сделал, поэтому как BoneCP уже 5 месяцев не поддерживается, правда, ему на смену пришёл HikariCP. Необходимо будет поглядеть, как дела в его исходниках… Пусть перегрузка на сервис и большая (от 800 до 2к запросов в минутку), но всё же приложение ведёт себя отлично, а это основное. В итоге я перевёл приложение на DBCP (пусть и версии 1.4).

Сражаемся с OOM
Я их отлавливаю до сих пор. Впечатлившись тем, как MAT мне всё разложил по полочкам, я решил не забрасывать этот действующий инструмент, и позднее он мне понадобился, поэтому как в первом приложении ещё остались всяческие «неучтёнки» — неучтённые вещи в коде приложения либо коде хранимых процедур, которые время от времени приводят к тому, что приложение складывает ласты.

Вооружившись обоими инструментами, я принялся ковырять каждый присланный дамп в поисках обстоятельств падения по OOM.
Как правило все OOM приводили меня к TaskThread.

И раз надавить на надпись See stacktrace, то да, это будет как раз очевидный вариант, когда какой-то поток вдруг в один момент свалился при попытке отмаршалить итог собственной работы.

Но тут ничто не показывает на причину появления OOM, тут только итог. Отыскать причину мне пока-что, в силу незнания всей магии OQL в MAT, помогает конкретно JVVM.
Загружаем дамп там, и пытаемся найти причину!

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

Два SybCallableStatement, и один SybPreparedStatement.
Думаю, что дело усложнится, раз Statement’ов будет куда больше, но незначительно подрихтовав один из последующих запросов, указав в where нужные условия, думаю, всё у Вас получится. Плюс, естественно же, стоит хорошо поглядеть в MAT, что за результаты пробует отмаршалить поток, какой объект, и станет понятнее, какой конкретно из Statement’ов нужно находить.

select {
instance: x,
stmtQuery: x._query.toString(),
params: map(x._paramMgr._params, function(obj1) {
if (obj1 != null) {
if (obj1._parameterAsAString != null) {
return »’+obj1._parameterAsAString.toString()+»’;
} else {
return "null";
}
} else {
return "null";
}
})
}
from com.sybase.jdbc4.jdbc.SybCallableStatement x
where x._query != null

Не то, это «внутренние» вызовы.

select {
instance: x,
stmtQuery: x._query.toString(),
params: map(x._paramMgr._params, function(obj1) {
if (obj1 != null) {
if (obj1._parameterAsAString != null) {
return »’+obj1._parameterAsAString.toString()+»’;
} else {
return "null";
}
} else {
return "null";
}
})
}
from com.sybase.jdbc4.jdbc.SybPreparedStatement x
where x._query != null

А вот и дичь!
Эти два миллиона даже влазят в память приложения, но вот попытка отмаршалить итог становится фатальной для приложения. Такое для себя харакири. 🙂 Для чистоты опыта можно кинуть таковой же запрос в возлюбленной БД-IDE, и он будет чрезвычайно долго отрабатывать, а раз покопаться в недрах хранимки, то будет понятно, что там просто из базы, которая нам не принадлежит, выбирается 2 миллиона строк по такому запросу с таковыми параметрами.
При этом GC старательно убирает все улики, но не выручило его это, всё же источник остался в памяти, и он будет наказан.

Почему-то опосля всего этого рассказа ощутил себя тем ещё неудачником.

Прощание
Вот и закончилось моё повествование, надеюсь, Для вас понравилось 🙂
Считаю эти новейшие познания чрезвычайно полезными. Желал бы выразить благодарность собственному начальнику, он отдал мне время во всём этом разобраться.
Спасибо девушкам из Scorini за постоянно вкусный кофе, но они не прочитают этих слов благодарности — я даже сомневаюсь, что они знают о существовании Хабрахабра 🙂
Хотелось бы узреть в комментах ещё больше полезной инфы и дополнений, буду чрезвычайно благодарен.

Думаю, самое время почитать документацию к MAT…

UPD1: Да, совершенно запамятовал поведать про такие полезные вещи, как создание дампов памяти.
docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/clopts.html#gbzrr
Функции
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/disk2/dumps
очень полезны для генерации дампов в момент падения приложения по OutOfMemoryError,
а также существует возможность снять дамп памяти с приложения «наживо», среди его работы.
Для этого существует утилита jmap.
Пример вызова для винды:
«C:installPSToolsPsExec.exe» -s «C:Program FilesJavajdk1.7.0_55binjmap.exe» -dump:live,format=b,file=C:dump.hprof 3440
крайний параметр — это PID java-процесса. В случае, когда возникает OOM, чистить память незачем, там уже не осталось мусора, так что не отыскиваете, как можно установить опцию live в случае появления OOM. habrahabr.ru Функция live полезна, чтоб перед сохранением дампа вызвать GC, очистив память от мусора. Приложение PsExec из набора PSTools дозволяет запускать остальные приложения с правами системы, для этого служит ключ "-s".