K napsání tohoto článku mě inspiroval Ondra Medek svými maily v javovské
konferenci v lednu 2010, v nichž se podivoval nad tím, že Java automaticky
neuklízí zavřená okna. Pokud okno nemá nastaveno DISPOSE_ON_CLOSE,
je při zavření pouze schováno a nadále zabírá paměť. K jeho dealokaci
dojde až při zavolání metody dispose(). V tomto článku si
ukážeme, jak lze pomocí AspectJ sledovat okna v našem programu.
Okno vzniká voláním konstruktoru a zaniká voláním metody
dispose(). Využijeme toho, že AspectJ umožňuje
připojit advice před i za volání konstruktoru a metody.
Pointcut pro konstruktor třídy Window
a libovolného potomka definujeme takto (konstruktor může mít
libovolné parametry):
pointcut newWindow(): call(Window+.new(..));
Pointcut pro metodu dispose() ve třídě
Window a libovolném potomkovi zapíšeme následovně:
pointcut disposeWindow(): call(public void Window+.dispose());
Bezprostředně po provedení konstruktoru si uložíme hashCode
okna spolu s informacemi, kde (číslo řádku) a jak (signatura konstruktoru)
bylo okno vytvořeno. Tyto informace nám pomůžou, až se budeme snažit
vysledovat okno, které nebylo dealokováno.
Pro ukládání informací použijeme dva seznamy:
seznam windows pro hashCode a
seznam info pro dodatečné informace o okně.
after() returning(Window w): newWindow() {
int h = w.hashCode();
windows.add(h);
String s = String.format("%d: %s, %s", h, thisJoinPoint.getSourceLocation(),
thisJoinPoint.getSignature());
info.add(s);
}
Při dealokaci (dříve než dojde k zavolání dispose()) informace
o zavíraném okně ze seznamů odstraníme.
before(): disposeWindow() {
int h = thisJoinPoint.getTarget().hashCode();
int i = windows.indexOf(h);
windows.remove(i);
info.remove(i);
}
Dále přidáme výpis seznamu oken v pravidelných intervalech a na konci metody main:
pointcut main(): execution(public static void main(String[]));
before(): main() {
Integer i = Integer.getInteger("interval");
if (i != null) {
new Timer(true).schedule(
new TimerTask() {
public void run() {
printLiveWindows();
}
}, i, i
);
}
Runtime.getRuntime().addShutdownHook(
new Thread(
new Runnable() {
public void run() {
printLiveWindows();
}
}
)
);
}
Celý aspect je k dispozici zde.
Chcete-li jej použít ve svojí aplikaci, nainstalujte si
AspectJ a aspect
i zdrojáky přeložte překladačem ajc.
Např. takto:
ajc -1.6 monitoring/WindowAspect.aj app/*.java
Aspect bude fungovat jen ve třídách, které s ním byly přeloženy.
Necháte-li tedy vytváření oken na nějakém frameworku, aspect toto
nezachytí. Funguje to zhruba tak, že při překladu pomocí ajc
se přidá za každé volání konstruktoru a před každé volání dispose()
volání našeho kódu.
Pokud si chcete aspect jen vyzkoušet, stáhněte si ukázkovou
aplikaci (testapp.jar) a běhovou podporu
pro AspectJ (aspectjrt.jar).
Aplikaci pustíte příkazem
java -jar testapp.jar
Chcete-li vypisovat informace o oknech průběžně, použijte příkaz
java -Dinterval=20000 -jar testapp.jar
Časový údaj je v milisekundách. Soubor aspectjrt.jar musí být v aktuálním adresáři.
www.amaio.com