Tomcat redeploy -> outofmemory

Fórumok

Az is lehet, hogy az alapokkal van gondom, de jobb későn mint soha.

Adott egy program.
Ennek egy szervlet initjében betöltök egy statikus class egyik statikus metódusával a statikus class statikus változójába értéket (nagy fájlt cachelek).

Mielőtt stop / startot kiadok a tomcat alatt, csak 1 példányt látok a memory dump-ban.
Ha kiadok egy stop / startot, akkor 2 példány lesz a statikus class-ból a memory dumpban. Természetesen mindkettőnek van mérete.

Hogyan lehetne elkerülni ezt? Cachelni muszáj, Tomcat 6 és 7 alatt is próbáltam.

Hozzászólások

Attól, hogy cache-elsz, még nem kötelező statikus változóba tenni. Keress, vagy csinálj, valami ojjektumot, aminek van tisztességes életciklus menedzsmentje, és abba tedd.

Amúgy a statikus dolgokat is ki tudja dobni a Java, csak nem biztos, hogy Tomcat-en működik. OSGI alatt működne az is, leglábbis régen teszteltem ilyesmit.

ugye standard beugrokerdes Javas allaskorbe, hogy akkor egy singletonbol hany peldany is lehet a memoriaban? :)

Ha Tomcat 7-tel próbálkozol és leállítod a webalkalmazásodat, nem panaszkodik a Tomcat a logjában, hogy memory leakelsz (warning leállítatlan szálakról, kitakarítatlan thread localokról)? Használsz ThreadLocal-t? Heap vagy PermGen kevés? Referálod a cachelt fájlt esetleg máshol is a statikus változón kívül?

1.
Panaszkodik, de nem erre, hanem az EhCache-ra, valamint a psql jdbc driverre:

SEVERE: The web application [] registered the JDBC driver [org.postgresql.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
2011.03.18. 16:32:50 org.apache.catalina.loader.WebappClassLoader clearReferencesThreads
SEVERE: The web application [] appears to have started a thread named [net.sf.ehcache.CacheManager@d61aef] but has failed to stop it. This is very likely to create a memory leak.

Most átállítottam, hogy build-nál ne vigye át a psql drivert, valamint a classloader-t beállítottam, hogy a contextnél is a system loader legyen, így ezek megszűntek, viszont a 2x deploy még mindig nem megy. Még mindig duplán van ez a class a memóriában. :-(

Most a tomcat manager ezt válaszolja a "find leaks" buttonra:
"No web applications appear to have triggered a memory leak on stop, reload or undeploy."

2.
Nem használok ThreadLocal-t, hacsak valamelyik lib nem.

3.
Heap kevés
FAIL - Encountered exception java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Java heap space

4.
Egy másik osztályból is meghívok egy metódust a static classból, ami visszaadja a cache értékét.
Itt esetleg lehet gáz, mivel itt classloaderrel töltöm be dinamikusan az erre hivatkozó osztályt.
Bár, ha nem jut el a betöltésig, akkor is ugyanez a helyzet.

Szerintem nálad egy ilyen trükkösebb memory leak van, ami a class loaderekkel függ össze és igazából nem a static cache az oka, az csak hamarabb előhozza. Erre gyanakodtam az előbb is, de az 1 és 4 pontok megerősítettek.

Javaban classloaderek töltik be a classokat. A classloader referálja az általa betöltött class-okat, a classok is megjegyzik, hogy melyik loader töltötte be őket.

A tomcat minden webalkalmazáshoz létrehoz egy webappclassloader-t és azzal tölti be az osztályokat, amik az adott webapp-pal együtt jönnek (/WEB-INF/classes meg /WEB-INF/lib alatt). Stop-nál a tomcat egyszerűen eldobja a webappclassloader instance-t és így, jól viselkedő webalkalkalmazásnál, a classloader-rel együtt az összes általa betöltött class és az összes statikus változó (amik igazából class változók) felszabadíthatóak. Sajnos van néhány speciális pont (jdbc driver registry, thread local, thread, stb.), amikor a jvm egyéb szolgáltatásai képesek megtartani egy referenciát az egyik webapp-ban betöltött class-ra, vagy változóra (a tomcat ezek egy részét képes észlelni, és egy részét javítani is, ezeket warningolja). Így a következő függőség jön létre: [vmi jvm szintű dolog] -> webappból származó class -> webapp classloader -> összes webappban betöltött class -> ezek statikus változói. Ebben a láncban a tomcat hiába felejti el a webapp classloader-t a lánc fennmarad és az eredmény egy szép memory leak.

Megteheted, hogy a statikus változókat átviszed instance változókba, de ezzel csak átmeneti javulást kapsz, az előbb írt láncból csak a végét csapod le. Ráadásul így valószínűleg már nem heap fog elfogyni, hanem a permgen (mert a class objektumok ott laknak).

Másik lehetőség, hogy meg kell szüntetni minden speciális függést. A memory dumpból látszania kell, hogy ki tartja a class-t vagy a classloader-jét (esetleg használj eclipse mat plugint memory dumpot jól lehet vele elemezni).

Ami a jdbc drivert illeti: álmoskönyv szerint azt a tomcat lib-be kell rakni a webapp libje helyett. De ezt a tomcat orvosolja (törli maga unloadnál).
"valamint a classloader-t beállítottam, hogy a contextnél is a system loader legyen": én nem babrálnám a tomcat class loaderjeit, jól működnek azok alapbeállítások mellett. Úgy kellene megjavítani a webappot, hogy a default beállításokkal működjön jól.

Ok, kiirtottam mindent a servletből, csak az init, van, abban is csak ezt az 1 hívást végzem.

Most elindítom, majd leállítom. Sajnos így is kiakad.

FAIL - Encountered exception java.lang.RuntimeException: java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: GC overhead limit exceeded

hali! ha debuggolod a jvm-et, pontosabban ha van aktív agent, akkor könnyen előfordulhat, hogy permgent nem szabadít fel.

szerk. oké csak most olvasom a hozzászólásodat, mindesetre akkor csak egy adalék

Úgy néz ki, hogy a hiba mégsem ezen a környéken van, ha kiveszek egy filtert a we.xml-ből, minden normális. Nyomozok tovább...

Úgy néz ki, hogy a filterben a log4j volt a hibás. Átálltam java loggerre és ott rendbejött.

Még van egy gond az EhCache körül is, ha kiveszem ezeket a sorokat, akkor megint jó:

_fCache = EhCacheTools.createCache("fCache", 100);
_aCache = EhCacheTools.createCache("aCache", 100);

Az EhCache-t viszont nem értem.

Itt így jön létre a cache:

_singletonManager = CacheManager.create();
Cache memoryOnlyCache = _singletonManager.getCache(name);

return memoryOnlyCache;

A servlet destroy metódusában pedig biztos, ami biztos meghívom:
CacheManager.getInstance().shutdown();

Ennek ellenére, ha pl. egy ConcurrentHashMap-el helyettesítem, akkor simán tudom használni az alkalmazást, cachel stb. (csak nem törlődik a cacheből semmi), míg a ha berakom, hogy EhCache-t használjon redeploynál kiakad, ha már volt használva a cache.

Ötletek?