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.