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í.