PermGenSpace problem? No problem!
Tento článek vyšel na našem firemním intranetu. Jelikož je jeho obsah velmi přínosný ve své jednoduchosti a agregace poznatků z řady roztříštěných zdrojů po internetu, požádal jsem autora Michala France o svolení k jeho zveřejnění. Jak to dopadlo, můžete vytušit už sami. Výsledkem je že se s Vámi mohu podělit o zkušenosti s (vy)řešením problémů OutOfMemory v oblasti PermGenSpace při redeploy našich aplikací v aplikačních kontejnerech. Před aplikací těchto znalostí jsme vcelku pravidelně po dvou “redeployích” restartovali celý server, protože docházela PermGenSpace. V současném stavu aplikační server žije i po několika desítkách redeployů.
A nyní slíbený článek
Každého programátora to jednou čeká. Jeho aplikace začne padat na OutOfMemoryError. Dá se krčit rameny se slovy “já vážně nevím čím to je”, nebo s tím něco udělat.
Tak mu dej víc paměti
V ideálním případě je chyba způsobena pouze poddimenzovaným nastavením limitů paměti. Pak stačí nastavit JVM následovně (hodnoty doplnit dle uvážení).
Pro java.lang.OutOfMemoryError: Java heap space
Pro java.lang.OutOfMemoryError: PermGen space
Další volby v dokumentaci Java HotSpot VM.
Tak tohle nepomohlo, prubni jmap
Většinou ale zvýšení přiřazené paměti problém jen oddálí. Pak přijdou na řadu diagnostické nástroje. Tím nejjednodušším je jmap.
Pomocí jmap je možné získat užitečné informace o využití paměti a umožňuje získat HEAP dump.
Více informací o jmap s příklady naleznete v sumarizaci na Blog O’Matty
S jmap jsem narazil na problém pod Windows Vista. Nepodařilo se mi ho donutit komunikovat s java procesem, který běžel jako service. Pořád tvrdošíjně tvrdil, že „Not enough storage is available to process this command”. Jediné řešení které znám je spustit proces přímo z příkazové řádky.
Můžu zjistit něco i bez jmap?
Na JDK 1.5 lze použít následující JSP (je možné ho nakopírovat přímo do deploynuté aplikace)
Výpis informací může vypadat nějak takhle:
Co je to ten perm gen?
Telegraficky – popis jednotlivých oblastí paměťi JVM (formulace dávají přednost srozumitelnosti před přesnou charakteristikou):
- Eden Space – oblast kde jsou instance objektů umístěny po jejich vytvoření (young generation)
- Survivor Space, Tenured Gen, From/To space – instance objektů s delší platností
- Perm Gen – interní data JVM, zde se ukládají načtené třídy (class)
- Code Cache – oblast obsahující bytecode přeložený do nativní podoby

Více se dá dočíst v Tuning Garbage Collection with the 5.0 Java Virtual Machine
Paměti to žere dost, ale kde?
Přehled o objektech v paměti lze získat pomocí profilerů. Našinec asi vynechá klasickou komerční řadu produktů (YourKit, JProbe, JProfiler) a bude hledat něco zdarma (NetBeans Profiler). Problémy se ale nejčastěji objevují na produkčních prostředích. A zde je použití profilerů mnohem složitější. Většinou zbývá jediná možnost, získat dump paměti a ten následně analyzovat.
Použití jmap
Příklady použití jmap pod Linuxem
A pod windows (JDK 1.6)
Hodnota 18302 je číslo procesu (PID).
Dump přímo z JVM
Následující užitečná nastavení:
JVM provede heap dump pokud dojde k OutOfMemoryError
JVM provede heap dump pokud obdrží Ctrl Break
Generování dumpu může trvat poměrně dlouho (jednotky minut), proto pozor v jaké době dump děláte. JVM v průběhu generování samozřejmně nepracuje.
Mrkneš se na ten dump? Já se v tom nevyznám.
Zbývá už „jen zanalyzovat” jaké objekty jsou v paměti. Lze využít některý z profilerů, ale jsou zde i další nástroje.
jhat – Java Heap Analysis Tool
Příklad použití (s nastavením 512MB pro jhat).
JHAT nastartuje webovou aplikaci. Nasměrujte prohlížeč na http://localhost:7000/


Sap Memory Analyzer
Tento nástroj je skutečně to nejlepší co se mi podařilo pro analýzu heap dumpu najít.
Program je volně ke stažení zde.
To neřeš, perm gen dochází jen po redeploy
Častý problém – na aplikačním serveru dochází Perm Gen po několika restartech aplikačního kontextu.
Na serveru nedochází k uvolnění classloaderů a v perm gen zůstávají třídy staré verze aplikace (více popisuje Frank Kieviet).
Ano i já jsem objevil již objevené.
Problém první – commons logging
Problémy s commons-logging jsou dobře popsané, celkem i srozumitelné, ale přece jen. Vzhledem k tomu, že existuje jednoduchý způsob jak se commons-logging úplně zbavit, proč to neudělat.
SLF4J umí nahradit commons logging beze změny v aplikaci, pouze nahrazením knihoven. S Maven je řešení úžasně jednoduché. Jediný problém je, jak zajistit aby se do výsledného buildu nedostal commons-logging z tranzitivních dependencí. Řešení je nastavit scope na provided.
Příslušná část pom.xml pak vypadá následovně:
Problém druhý – JDBC drivery
DriverManager v JDBC způsobuje další leak. Pokud nelze přesunout JDBC drivery do knihoven aplikačního serveru, je zde možnost použít context listener pro uvolnění registrovaných driverů:
Uvedené řešení, stejně jako problémy s dalšími knihovnami naleznete v článku Memory leak – classloader won’t let go.
Asi se zeptáte proč vše neřešit vhodným umístěním knihoven přímo do aplikačního serveru. Zní to jednoduše, nicméně úmyslně jsem řešil problém tak, aby aplikace nebyla závislá na konfiguraci prostředí.
Dovětek
Na závěr ještě doporučuji JavaTM 2 Platform – Troubleshooting and Diagnostic Guide a A day in the life of a memory leak hunter.
Většina nástrojů je závislá na JDK 1.5, dump lze získat i z posledních verzí JDK 1.4.2. Pokud někdo víte jak vymámit dump z JDK 1.4.2_04 podělte se prosím.
Pro ne-Sunovské implementace JVM může být vše jinak
Update k 9.5.2008 – další způsoby jak leakovat classloader
Následným zkoumáním našich web aplikací jsme přišli na další způsoby, jak spolehlivě přijít o možnost GC classloaderu:
-
Leakování Java Timeru
při použití Timerů ze standardního balíku Javy se často používá strategie, že při naběhnutí aplikace se nastartují vlákna, která v pravidelných intervalech provádějí určité servisní činnosti. Častou chybou ale je, že při ukončení aplikace, se nastartované Timery neuzavřou. Tím pádem zůstanou instance vašich objektů živé i po stopnutí web aplikace a zamezí zGC classloaderu celé web aplikace.
-
Leakování referencí v ThreadLocal
další poměrně často využívaná technika ve web aplikacích je používání ThreadLocal objektů, držících reference na objekty po celou dobu zpracování requestu. Pokud se tyto proměnné na konci zpracování nevyčistí, může v nich zůstat reference na objekt z web aplikace, což opět zabrání zGC jejího classloaderu. Tuto chybu obsahuje například i knihovna iBatis – chyba je již nahlášená z roku 2006, takže kdo ví, jestli se jejího vyřešení dočkáme.
Update k 19.6.2008 – způsobů jak leakovat je na tisíc
-
Leakování class v Introspectoru
Pokud používáte (nebo některá z knihoven) JavaBeany a přistupuje te k nim reflexním způsobem (tzn. používáte např. Commons BeanUtils, Spring, nebo další frameworky, které pracují s JavaBeanami) je třeba starat se o vyčištění Introspector cache – což je cache, která udržuje meta informace o třídách, jejich property a dalších věcech, které jsou potřeba. Cache je životně důležitá z hlediska performance. Bohužel Introspector sídlí v classloaderu Javy a tudíž mimo kontext classloader webové aplikace. Classy uložené v cache tedy zabrání GC classloaderu web aplikace. Pokud používáte spring můžete využít IntrospectorCleanupListener, který je ale potřeba zaregistrovat do web.xml.
Zajímavé počtení najdeme v dokumentaci k tomuto listeneru:
Application classes hardly ever need to use the JavaBeans Introspector directly, so are normally not the cause of Introspector resource leaks. Rather, many libraries and frameworks do not clean up the Introspector: e.g. Struts and Quartz.
Cache lze vyčistit také jednoduše vlasním kódem zavoláním: Introspector.flushCaches();




(8 hlasů, průměrně: 4.75 z 5)
Žil jsem v dojmu, že naopak právě umístění knihoven do aplikačního serveru způsobuje problémy (objekt třídy nahrané classloaderem aplikačního serveru nebo bootstrap classloaderem má referenci na objekt třídy nahrané classloaderem aplikace) a že by se knihovny měly umisťovat přímo k aplikaci. Jak to ksakru je?
To všechno platí až na malé vyjímky ;o)
Chápu to tak, že problematická je přítomnost commons-logging v aplikačním serveru. Nebudu zastírat že v tom plavu a víc sem experimentoval.
Nicméně v odkazovaných článcích lze nějaké info nalézt:
http://blogs.sun.com/fkieviet/entry/classloader_leaks_the_dreaded_java
Not a problem if the Apache Commons code is loaded in your application’s classloader. However, you do have a problem if this code is also present in the classpath of the application server because those classes take precedence. As a result now you have references to classes in your application from the application server’s classloader… a classloader leak!
http://wiki.apache.org/jakarta-commons/Logging/UndeployMemoryLeak
Except for the brain-dead design of JDBC where jdbc drivers loaded via a custom classloader apparently get stored in a map within java.sql.DriverManager thereby causing cyclic references to that classloader.
Tak tenhle článek obsahuje spoustu hodnotného a vydestilovaného úsilí. Určitě ho nečtu naposled, díky moc pánové…
Děkuji za velmi pěkný článek,
se stejným problémem jsem se potýkal cca před 1,5 rokem ale vy jste byl mnohem preciznější
. Na problém s JDBC drivery a Springem jsem přišel také, ale externí knhovny jsem příliš nezkoumal. Zajímalo by mě, do jaké míry se Vám podařilo problém odstranit (nebo zmizel úplně?). Pokud si vzpomínám, tak jsem tehdy došel do celkem rozumného stavu, kdy mi Tomcat lehnul až po několika desítkách restartů.
Mám drobnou poznámku, odkaz na RSS kanály úplně dole ve stránce není správný a měl by být nejspíš takový http://blog.novoj.net/feed/
Jakube díky za info, ty linky vytváří FeedBurner plugin a přestože vypadají “zvláštně”, jsou funkční (přesměrovávají na RSS generované FeedBurnerem). Navíc např podle článku: http://www.brindys.com/winrss/feedformat.html se zdá, že odkaz je i správně.
Současný status quo je ten, že problém se nám 100% odstranit nepodařilo. Po úvodním nadšení jsme zjistili, že problém se podařilo odstranit na testované instalaci, nicméně na jiných přetrvával. Od té doby dopisuji k článku další problémy, na které jsme narazili a které postupně odstraňujeme.
Děkuji za tento článek. Doplňuji další odkazy, kde je problematika také velmi hezky popsána:
http://blogs.sun.com/fkieviet/entry/classloader_leaks_the_dreaded_java
http://blogs.sun.com/fkieviet/entry/how_to_fix_the_dreaded