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.