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.
