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
volá konstruktor
name)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
. Sledovat budeme pouze první čtyři.
fdObj)
Protože konstruktory FileOutputStream(File
,
file)FileOutputStream(String
a
name)FileOutputStream(String name,
volají konstruktor
boolean append)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í.