Pull to refresh

Comments 20

К сожалению это не так, грубо говоря, PermGen теперь просто называется Metaspace, и вы все равно получите OutOfMemoryError

Это в том случае, если вы его ограничиваете параметром командной строки, верно? Если не ограничиваете, то описания классов могут занимать хоть всю доступную приложению память. Или это не так?
Я не смотрел подробно как работает Metaspace в Java 8. Но даже если есть возможность дать всю доступную память для описания классов, утечка есть утечка. Рано или поздно она скушает всю доступную память. Другое дело, что в каком-то конкретном случае утечка может быть небольшая, OutOfMemoryError может случиться только после очень большого количества передеплоев, и тут надо решать, что целесообразнее: устранять утечку, или жить так.
>Я не стал глубоко копать и искать официальную документацию, а методом проб и ошибок определил, что для Java 7 и Tomcat 7 необходимо и достаточно добавить JVM опцию -XX:+UseConcMarkSweepGC

Довольно спорный совет. Включив это опцию вы меняете сборщик мусора на Concurrent Mark Sweep, а это может значительно повлиять на работу приложения. При этом возможность выгружать классы во время работы приложения вы получаете просто как бонус.
Если говорить о других сборщиках мусора, то там обычно сборка мусора в PermGen выполняется при Full GC.
Согласен с вами, что менять алгоритм GC просто потому что так заработало — спорно. В свое оправдание могу сказать, что CMS вроде бы является рекомендуемым для продакшна (пруф1, пруф2). Ну и если вы дадите ссылку на документацию относительно сборки мусора в PermGen с разными алгоритмами сборки — буду очень благодарен.
Первая ссылка:
The CMS garbage collector is the first and most-widely used low-latency collector

Ключевое слово здесь low-latency. Используя CMS вы улучшаете latency, но ухудшаете throughput. Грубо говоря, вы жертвуете производительностью, зато избегаете длинных GC пауз.

По второй ссылке — вы бы еще древнее что-нибудь нашли! Статья времен WebLogic Server 9.2, 2006 г. Да и что-то там c настройками перемудрили — XX:SurvivorRatio=128 — это что-же survivor space занимает 32/130MB ~ 250KB?

По сути, вы можете выполнить прекрасную команду в консоли
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version | grep Unloading
Она покажет вам все опции, влияющие на unloading классов. По дефолту ClassUnloading включена, т.е. по факту классы в permgen будет собираться. А вот как раз если вы используете CMS GC, то для unloading'а классов вам нужно включить -XX:+CMSClassUnloadingEnabled.

А вообще по теме GC рекомендую отличный доклад от Владимира Иванова , а на недавней конференции Joker как раз был доклад про утечки памяти.
Насколько я знаю -XX:CMSClassUnloadingEnabled включена по дефолту, при использовании CMS (на не очень древних версиях Hotspot).
На моей машине

jdk1.7.0_45
bool CMSClassUnloadingEnabled = false {product}

jdk1.8
bool CMSClassUnloadingEnabled = true {product}
Посмотрел как ведут себя разные сборщики мусора с PermGen-ом:
Parallel (дефолтный на моей машине) — PermGen не очищается, падает OutOfMemoryError
Serial — PermGen очищается после достижения 100%
Concurrent Mark Sweep — PermGen очищается после достижения 100%

Вызвать очистку PermGen вручную мне не удалось, на System.gc() не реагирует.

При этом
bool CMSClassUnloadingEnabled = false {product}
bool ClassUnloading = true {product}

Все-таки категорически не хватает документации.

JDK 1.7.0_55-b13, Windows7, amd64, Intel Core i5 650 @ 3.20GHz
Тесты в студию!

Собственно, вот мои тесты: gist.github.com/SerCeMan/876fcdfaca602af2df8d

Судя по тесту, классы выгружаются при любом GC, даже если мы опция CMSClassUnloadingEnabled стоит false.

Конечно же нужно забывать отдавать ClassLoader на откуп сборщику, все таки он хранит в себе ссылки на все загруженные классы.
Я тестировал на реальном приложении, передеплоивал его в томкате, выложить не могу по понятным причинам, но думаю можно собрать простенькое веб-приложение со спрингом и получить те же результаты.

С вашим тестом классы действительно выгружаются с любым gc.
Кстати, вот более простой вариант теста gist.github.com/zgmnkv/0e7ac1fe91c7d51d41a3
Попутно более-менее прояснил значение опций:
ClassUnloading — если true, то классы выгружаются; если false, то не выгружаются, и падает OutOfMemoryError, логично в общем то.
CMSClassUnloadingEnabled — влияет только на CMS GC; если true, то классы выгружаются чаще, PermGen не доростает до максимума.
Совсем недавно на CodeFreeze была встреча на эту тему. Только внимание было уделено Eclipse Memory Analyzer.
> Также популярным вариантом утечки является ThreadLocal переменная, которой присвоен объект из веб-приложения для потока из общего пула. В этом случае поток хранит ссылку на объект.

Это совершенно неверно. Поток нигде не хранит ссылку на объект. Внутри ThreadLocal использует структуру похожую на Map для сопоставления объекта с потоком. Сборщик мусора соберет ваш ThreadLocal и все его объекты также как обычный HashMap. При этом потоки естественно останутся нетронутыми.
Почему вы так решили, даже не заглянув в исходники? ThreadLocal внутри себя хранит ровно одно нестатическое поле — это int threadLocalHashCode. Сами данные лежат в Thread.threadLocals. Структура ThreadLocalMap действительно похожа на HashMap (точнее это мультимэп), вот только ключи — это не потоки, а идентификаторы переменных. Автор поста как раз прав.
По факту там используются слабые ссылки на сами ThreadLocal, но это не решает всей проблемы: объект ThreadLocalMap.Entry хранит также сильную ссылку на значение. Поэтому даже если ThreadLocal протух, и Entry теперь ни на что не ссылается, сам Entry существует, пока ThreadLocalMap явно не освободит этот слот, что может произойти после нескольких обращений к другим ThreadLocal-переменным в том же треде. Если этого никто делать не будет, значение из исходного ThreadLocal (и всё, что из него растёт) останутся сильно связанными.
Сорри, действительно. Entry у ThreadLocalMap — это WeakReference на ThreadLocal, так что ThreadLocal все-таки собирается GC. Странность реализации заключается в том, что Entry и value продолжает храниться в таблице, пока Thread не вызовет не set() для любой TheadLocal.
Собственно, вся сила ThreadLocal как раз в том, что они не требуют конкурентного доступа: никаких проблем при одновременной записи из десяти тредов, данные полностью разделены. Реализация вида ConcurrentHashMap<Thread, Object> была бы во много раз медленнее из-за рехэшинга разделённого объекта, возможности попасть двум тредам в одну корзину и так далее.
Это каждый раз надо делать?
В общем как человек который уже много лет пишет на яве могу сказать что я не использую несколько приложений на одном tomcat
У нас работает несколько экземпляров jetty одного и того же приложения чтобы распределять нагрузку. Запросы распределяет nginx.
Мне казалось так все делают.
Что вы имеете ввиду под «каждый раз»? При разработке нового приложения?
Это дело каждого, если вам мешают такие ошибки — исправляйте их, если не мешают — не исправляйте. Если приложение написано правильно, завершает все созданные потоки, убирает проставленные ThreadLocal, не проставляет свои ссылки в системные объекты, то у вас не должно возникать подобных утечек.
По поводу распределения нагрузки — это из другой оперы. Я имел ввиду, что разные приложения или разные части одного приложения задеплоены в одном tomcat-е.
Sign up to leave a comment.

Articles