V tomto článku se podíváme se na to, jak je možné pomocí nástroje BTrace sledovat vytváření a úklid oken v javovském programu. Budeme sledovat konstruktory a metodu dispose, která provádí úklid. Pokud programátor zapomene zavolat metodu dispose, nastává memory leak. Dále si ukážeme, jak sledovat otevřené soubory. BTrace nás bude informovat o každém otevření či zavření souboru a na naši žádost vypíše seznam právě otevřených souborů. Tyto postupy mohou pomoci odhalit dvě běžné programátorské chyby: neuklizené okno a neuzavřený soubor.

BTrace je nástroj pro sledování javovských programů, který používá dynamickou instrumentaci javovského bajtkódu. Dokáže se připojit k běžícímu programu, změnit jeho třídy a změněné třídy programu podstrčit. BTrace např. může přidat volání našeho kódu při vstupu do metody nebo před opuštěním metody. Kód, který chceme takto "přidat" k existujícímu programu, zapisujeme do statických metod ve třídě, která je označena anotací @BTrace. Anotací @OnMethod na metodě vybíráme přípojné místo v programu. Následující příklad vytiskne "print entry" při každém vstupu do metody print ve třídě first.Test.

 

@BTrace
public class PrintMonitor {

    @OnMethod(
        clazz = "first.Test",
        method = "print",
        location = @Location(Kind.ENTRY))
    public static void onPrint() {
        BTraceUtils.println("print entry");
    }
}

 

Toto využijeme při sledování oken. Každé okno v javovském programu je instancí třídy java.awt.Window nebo nějakého potomka. Při vytváření okna tedy vždy dojde k zavolání kontruktoru třídy Window. Stačí tudíž sledovat konstruktory této třídy. Abychom měli přehled o vytvořených oknech, budeme si je ukládat do mapy. Klíčem v mapě bude hashCode daného okna a hodnotou bude obsah zásobníku v době vytváření (tím si zapamatujeme, kde k vytvoření okna došlo).

Uvolnění prostředků okna provádí metoda dispose. Dokud ji programátor nezavolá, okno nemůže být uklizeno. Metodu dispose budeme sledovat na všech potomcích třídy Window. Po skončení metody odstraníme záznam o okně z mapy.

 

@BTrace
public class WindowTracker {

    private static Map<Integer, String> windows = Collections.newHashMap();

    @OnMethod(
        clazz = "java.awt.Window",
        method = "<init>",
        location = @Location(Kind.RETURN))
    public static void onNewWindow(@Self Window self) {
        int hash = BTraceUtils.hash(self);
        String s = BTraceUtils.concat("opened: ", BTraceUtils.str(hash));
        BTraceUtils.println(s);
        String stack = Threads.jstackStr(4);
        Collections.put(windows, BTraceUtils.box(hash), stack);
    }

    @OnMethod(
        clazz = "+java.awt.Window",
        method = "dispose",
        location = @Location(Kind.RETURN))
    public static void onDisposeWindow(@Self Window self) {
        int hash = BTraceUtils.hash(self);
        String s = BTraceUtils.concat("disposed: ", BTraceUtils.str(hash));
        BTraceUtils.println(s);
        Collections.remove(windows, BTraceUtils.box(hash));
    }

    @OnEvent
    public static void printWindows() {
        BTraceUtils.printMap(windows);
    }
}

 

Metodu označenou anotací @OnEvent můžeme vyvolat po spuštění programu btrace pomocí Ctrl+C a druhé položky z menu. Metoda vypíše všechna otevřená okna, na kterých dosud nebyla zavolána metoda dispose.

Pokud si chcete sledování oken vyzkoušet, nainstalujte si BTrace a stáhněte si zdroják WindowTracker.java. Pro vyzkoušení můžete použít ukázkovou aplikaci TestApp.jar. Nejprve pustíte ukázkovou aplikaci

java -jar TestApp.jar

a pak se k ní připojíte příkazem

btrace pid monitoring/WindowTracker.java

pid zjistíte např. programem jps z JDK.

Pro sledování souborů využijeme třídy java.io.FileInputStream a java.io.FileOutputStream. Tím budeme sledovat nejen instance těchto tříd, ale např. i java.io.FileReader a java.io.FileWriter, protože tyto třídy používají interně FileInputStream a FileOutputStream. Třída FileInputStream má tři konstruktory: FileInputStream(File file), FileInputStream(String name) a FileInputStream(FileDescriptor fdObj). Sledovat budeme pouze první dva (kód však lze snadno upravit i pro sledování třetího konstruktoru). Protože konstruktor FileInputStream(String name) volá konstruktor FileInputStream(File file), stačí nám sledovat pouze konstruktor s parametrem File. Třída FileOutputStream má pět konstruktorů: FileOutputStream(File file), FileOutputStream(File file, boolean append), FileOutputStream(String name), FileOutputStream(String name, boolean append) a FileOutputStream(FileDescriptor fdObj). Sledovat budeme pouze první čtyři. Protože konstruktory FileOutputStream(File file), FileOutputStream(String name) a FileOutputStream(String name, boolean append) volají konstruktor FileOutputStream(File file, boolean append), stačí sledovat jen tento jeden. Podobně by šlo sledovat i jiné třídy, jako např. java.io.RandomAccessFile.

Po skončení konstruktoru FileInputStream nebo FileOutputStream uložíme informaci o souboru do mapy a po provedení metody close tuto informaci odstraníme. Tím budeme mít v každém okamžiku přehled o všech otevřených souborech. Informace z mapy lze vypsat stejným způsobem jako v předchozím případě.

 

@BTrace
public class FileTracker {

    private static Map<Closeable, String> files = Collections.newHashMap();

    @OnMethod(
        clazz = "java.io.FileInputStream",
        method = "<init>",
        location = @Location(Kind.RETURN))
    public static void onNewFileInputStream(@Self FileInputStream self, File f) {
        String name = str(f);
        Collections.put(files, self, name);
        println(concat("opened for reading: ", name));
    }

    @OnMethod(
        clazz = "java.io.FileOutputStream",
        method = "<init>",
location = @Location(Kind.RETURN)) public static void onNewFileOutputStream(@Self FileOutputStream self, File f, boolean append) { String name = str(f); Collections.put(files, self, name); String s = append ? "opened for append: " : "opened for writing: "; println(concat(s, name)); }

 @OnMethod( clazz = "java.io.FileInputStream", method = "close", location = @Location(Kind.RETURN)) public static void onCloseFileInputStream(@Self FileInputStream self) { String name = Collections.remove(files, self); if (name != null) { println(concat("closed input file: ", name)); } }
@OnMethod( clazz = "java.io.FileOutputStream", method = "close", location = @Location(Kind.RETURN)) public static void onCloseFileOutputStream(@Self FileOutputStream self) { String name = Collections.remove(files, self); if (name != null) { println(concat("closed output file: ", name)); } }
@OnEvent public static void printOpenFiles() { println("Open files:");
printMap(files); println("--------------------"); } }

 

Pokud si to chcete vyzkoušet, stáhněte si FileTracker.java. K vyzkoušení je možné použít aplikaci Java2Demo z JDK (je v adresáři jdk/demo/jfc/Java2D). Když v tomto programu přepnete na záložku "mix", dozvíte se, že se zde opakovaně otevírá soubor README.TXT, aniž by docházelo k jeho uzavření.