Spring AOP - Pozor na AspectJExpressionPointcut!

Tento týden jsem řešil problém s nedostatkem paměti při spouštění testů jednoho projektu. Pro běh testů nestačilo výchozích 64MB paměti Javy na heapu, což mi připadlo v porovnání s velikostí projektu podezřelé. Začal jsem profilovat a jelikož mne výsledky poněkud překvapily, chci se s Vámi o ně v tomto článku podělit.

Hned na úvod řeknu, že jádrem problému byla třída AspectJExpressionPointcut. Tato třída je ve Spring dokumentaci zmiňována hned několikrát, velmi jednoduše se používá a ze všech dostupných materiálů jsem dospěl k názoru, že se jedná o doporučovaný a běžně používaný standard.

Identifikace problému

V mém případě jsem pomocí AOP ošetřil kolem deseti tříd (jednou kvůli implementaci security a podruhé kvůli transakcím), načež mi za integračním serveru začaly padat testy na OutOfMemoryError (Java heap space). Rozjel jsem si testy lokálně a došel jsem ke stejnému výsledku. Po spuštění testů s nastavením:

jsem z Javy dostal HeapDump v okamžiku kdy paměť došla a začal jsem dump analyzovat pomocí NetBeans Profileru. Okamžitě na mne vyskočilo, že třída java.lang.reflect.Method zabírá v paměti 15,2% místa o celkové velikosti téměř 10MB se 125tis. instancemi. Po krátkém zkoumání jsem zjistil, že většinu instancí drží právě AspectJExpressionPointcut ve své shadowMapCache. Tuto cache používá pro ukládání indexu, kde klíčem je právě Method a hodnotou je org.aspectj.weaver.tools.ShadowMatch, který udržuje informaci o tom, zda tato metoda odpovídá / neodpovídá deklaraci pointcutu. Celý index nepoužívá weak nebo soft reference a drží si objekty natvrdo.

Vysvětlení

Jistým uklidněním může být, že Spring testy se chovají k aplikačním kontextům poněkud odlišně, než je tomu v provozním systému. V testech se drží cache všech aplikačních kontextů z testů, které mají odlišnou “konfiguraci” (myšleno odlišné návratové hodnoty z getConfigLocations metody). Tím pádem je v paměti současně drženo větší množství kontextů (v mém případě jich bylo 24), které mají v sobě každý uvedené AspectJExpressionPoincuty, které defakto většinou drží stejnou shadowMapCache.

I tak se mi ale zdá, že je implementace AspectJExpressionPointcut poněkud nešetrná k paměti. Je důležité si uvědomit, že pro každou třídu je drženo v shadowMapCache tolik položek, kolik je metod dané třídy v celé hierarchii dědičnosti. Takže u mých “jednoduchých” DAO s deseti metodami, které dědí z org.springframework.orm.ibatis.support.SqlMapClientDaoSupport, jsem napočítal okolo 35 metod celkově. Při pronásobení se začínáme dostávat už na zajímavá čísla. Jen pro zajímavost jeden objekt typu java.lang.reflection.Method v paměti (podle profileru) drží 77B paměti. ShadowMatchImpl, který je jako hodnota v indexu, drží dalších 40 (celkově u mě dalších 4,5MB). Když jsem tedy sečetl cekovou náročnost AspectJExpressionPointcut pro mé testy zjistil jsem že se jednalo o 22,1% (6.9% + 15,2%) celkové obsazené paměti jen na tento jeden index!

Přestože se jedná o specifický případ v kombinaci s chováním Spring testů, jsem přesvědčen o tom, že masivnější používání AspectJExpressionPointcut v projektu může pozlobit i na produkci (zvlášť pokud byste v některých momentech měli více aplikačních kontextů najednou - jako je tomu v našem případě).

Řešení

Jakmile se podařilo objevit jádro problému, bylo řešení už jednoduché. Přepsal jsem deklarace AOP z použití AspectJExpression na implementaci standardních Spring AOP interfaců:

Dříve:

Nyní:

Problém samozřejmě zmizel.

Podělte se s ostatními:
  • Digg
  • del.icio.us
  • De.lirio.us
  • Technorati
Ohodnoťte článek:
Takovéhle články už radši ne!Nic nového pod sluncem.Průměr - obsahuje zajímavé střípky informací.Hodnotný článek - lecos nového jsem se dozvěděl.Skvělý článek - informace se mi dost hodí. (1 hlasů, průměrně: 5 z 5)
Loading ... Loading ...

3 reakcí to “Spring AOP - Pozor na AspectJExpressionPointcut!”

  1. Dagi:

    To je nemile zjisteni. Honzo, nepochopil jsem v cem se bude tenhle memory leak lisit v produkcnim nasezeni od toho testovaciho. Jestlize tam nemaji weak refernce, tak to GC neuklidi v kazdem pripade…

  2. Novoj:

    No rozdil je ten, že v testovacim, diky tomu jak Spring testy funguji je tech aplikacnich kontextu hafo. Tzn. drzi se tam ta shadowMapa nekolikrat. V produkci bude zridka vice aplikacnich kontextu nazivu naraz. Jenze, kdyz si zase uvedomim, ze jsem si timhle zpusobem proxoval pouze par trid (s rezervou do 20 trid), tak pri masivnejsim pouziti by to mohlo stejne znamenat potiz.

    Jeste jsem si rikal, jestli jsem nenapsal spatne tu expression, ze by mi to oproxovalo vic trid nez jsem chtel, ale ani po blizsim zkoumani mi nepripadlo. Btw. jak testujes AOP pointcuty? Napr. transakcni aspekty jsou ve vysledne aplikaci dost dulezite, ale hloupe se testuji. Uvazoval jsem o ziskani intanci bean a test na implementaci rozhrani SpringProxy nebo Advised, ale jeste jsem to nedomyslel do konce.

    Jen mne zajima tvůj (vas vsech :-) ) pristup.

  3. Dagi:

    Aha, myslis instanci objektu, ktere implementuji rozhrani org.springframework.context .ApplicationContext.

    Ty pointcuty napriklad pro transakce netestujeme nijak explicitne, navic v novem kodu pouzivame anotace pro demarkaci transakcniho chovani. Jine typy aspektu, v nasem pripade mame pomoci AOP udelane napriklad listenery, licencni kontroly a tusin audit, to lze udelat celkem snadno. V testu tam programove vrazim testovaci listener a pokud se mi zavola, tak je jasne, ze je ten aspekt namapovany spravne.

    Jeste me napadlo v souvislosti s temi transakcemi, tam by to slo udelat tak, ze by se clovek povesil v testu na PlatformTransactionManager a tam zkontroloval, ze se zavola getTransaction commit pripadne rollback. V rychlosti me napada, ze to zaveseni pro testy muze byt budto zase pomoci AOP ;-) a nebo si tam muzu udelat specialni implementaci PlatformTransactionManager, ktera to bude delegovat na skutecny TM a zaroven notifikovat nejake listenery.

Nechte zde svůj komentář

Opište prosím text z obrázku: