Pohled pod kapotu JVM – detekce čtení i zápisu do vybraného atributu s využitím rozhraní JDI

Pavel Tišnovský 4. 6. 2013

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy dokončíme téma, kterému jsme se začali zabývat již minule. Ukážeme si způsob využití rozhraní JDI (Java Debugger Interface) pro detekci čtení a současně i zápisu nové hodnoty do sledovaného atributu vybrané třídy.

Obsah

1. Pohled pod kapotu JVM – detekce čtení i zápisu do vybraného atributu s využitím rozhraní JDI

2. Dvě skupiny událostí, které lze využít v rozhraní JDI

3. Metody předepsané v rozhraní AccessWatchpointEvent

4. Metody předepsané v rozhraní ModificationWatchpointEvent

5. Registrace události typu AccessWatchpointEvent

6. Registrace události typu ModificationWatchpointEvent

7. Smyčka událostí se zpracováním jednotlivých typů událostí

8. Výpis zprávy v případě detekce události čtení sledovaného atributu

9. Výpis zprávy v případě detekce události zápisu do sledovaného atributu

10. Informace o lokaci ve zdrojovém kódu, kde došlo ke čtení či k zápisu do sledovaného atributu

11. Zdrojový kód demonstračního příkladu JDIWatchPointDemo3

12. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3

13. Testovací třída Test7 – zápis shodné hodnoty, operátor ++ apod.

14. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3 pro třídu Test7

15. Repositář se zdrojovými kódy demonstračního příkladu

16. Odkazy na Internetu

1. Pohled pod kapotu JVM – detekce čtení i zápisu do vybraného atributu s využitím rozhraní JDI

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy dokončíme téma, kterému jsme se podrobněji začali věnovat již minule. Jedná se o detekci čtení či zápisu do vybraného atributu určité třídy s využitím rozhraní JDI (Java Debugger Interface), což je funkcionalita implementovaná ve většině debuggerů a dalších ladicích nástrojů. Minule jsme si na dvojici demonstračních příkladů JDIWatchPointDemo1.java a JDIWatchPointDemo2.java ukázali způsob naprogramování reakce na událost typu AccessWatchpointEvent, která nastane ve chvíli, kdy dojde k přístupu ke sledovanému atributu (a to samozřejmě z libovolného vlákna). Slovem „přístup“ zde máme na mysli čtení atributu.

Dnes si pro doplnění popisu této problematiky ukážeme, jakým způsobem lze s využitím rozhraní JDI zjistit, že se změnila hodnota sledovaného atributu, tj. že došlo k operaci zápisu. Jak uvidíme dále, využívá se v tomto případě událost typu ModificationWatchpointEvent, která nám mj. zprostředkuje jak starou, tak i novou hodnotu sledovaného atributu. Díky této vlastnosti události ModificationWatchpointEvent není nutné, aby si debugger či podobná aplikace musela sama složitě udržovat hodnoty všech sledovaných atributů.

Poznámka: změnou atributu je v případě použití JDI myšlen i zápis takové hodnoty, která se shoduje s hodnotou původní.

2. Dvě skupiny událostí, které lze využít v rozhraní JDI

Připomeňme si, že typy událostí podporovaných v JDI lze rozdělit do dvou skupin. V první skupině najdeme události reprezentující změnu stavu cílové JVM jako celku a ve druhé skupině najdeme události, které jsou vztaženy ke konkrétnímu vláknu a ke konkrétnímu místu kódu, kde k události došlo. Do první skupiny náleží tyto typy událostí:

# Rozhraní Popis
1 com.sun.jdi.event.VMStartEvent Tato událost vznikne těsně před okamžikem, kdy je spuštěno hlavní vlákno aplikace, tj. většinou metoda main().
2 com.sun.jdi.event.VMDeathEvent Tato událost informuje debugger o tom, že cílová JVM bude ukončena. V závislosti na nastavení debuggeru mohou vzniknout dvě události tohoto typu.
3 com.sun.jdi.event.VMDisconnectEvent Tato událost informuje debugger o odpojení sledované JVM při jejím ukončování.
4 com.sun.jdi.event.ClassPrepareEvent Událost vygenerovaná ve chvíli, kdy se v cílové JVM načítá a připravuje třída.
5 com.sun.jdi.event.ClassUnloadEvent Událost vygenerovaná při odstraňování třídy ze sledované JVM (pokud JVM podporuje odstraňování tříd z paměti)
6 com.sun.jdi.event.ThreadStartEvent Událost informující debugger o tom, že vzniklo vlákno, které se bude spouštět (událost je vygenerována ještě před vlastním spuštěním vlákna)
7 com.sun.jdi.event.ThreadDeathEvent Událost informující debugger o ukončení nějakého vlákna

Ve druhé skupině typů událostí najdeme všechny události vztažené ke konkrétnímu vláknu a k určitému místu kódu. Tyto typy událostí jsou odvozeny od rozhraní com.sun.jdi.event.LocatableEvent:

Zápis nové hodnoty do sledovaného atributu
# Rozhraní Popis
1 com.sun.jdi.event.BreakpointEvent Vstup na breakpoint.
2 com.sun.jdi.event.ExceptionEvent Vznik výjimky (generováno v javovském kódu; pokud výjimka vznikne v nativním kódu, dojde ke vzniku události až později)
3 com.sun.jdi.event.MethodEntryEvent Vstup do metody
4 com.sun.jdi.event.MethodExitEvent Výskok z metody (libovolný return, ne ovšem při vzniku výjimky – tehdy se generuje jiná událost)
5 com.sun.jdi.event.WatchpointEvent Předek následující dvojice rozhraní:
6 com.sun.jdi.event.AccessWatchpointEvent Čtení hodnoty sledovaného atributu
7 com.sun.jdi.event.Modifica­tionWatchpointEvent
8 com.sun.jdi.event.Monitor­ContendedEnteredEvent Vstup do režimu čekání s právě uvolněným zámkem
9 com.sun.jdi.event.Monitor­ContendedEnterEvent Vstup do režimu čekání se zámkem, který již vlastní jiné vlákno
10 com.sun.jdi.event.MonitorWaitedEvent Vlákno ukončilo čekání na synchronizaci (i timeout čekání)
11 com.sun.jdi.event.MonitorWaitEvent Vlákno vstoupí do režimu čekání na synchronizaci (monitor)
12 com.sun.jdi.event.StepEvent Jeden krok při krokování aplikace

V dalším textu nás bude z předchozí tabulky zajímat pouze řádek 5, 6 a 7.

3. Metody předepsané v rozhraní AccessWatchpointEvent

V rozhraní com.sun.jdi.event.AccessWat­chpointEvent nalezneme metody shodné s metodami, které jsou předepsány v rozhraní com.sun.jdi.event.WatchpointEvent (což je předek):

# Návratový typ Jméno metody Parametry Význam
1 com.sun.jdi.Field field () vrací objekt představující sledovaný atribut
2 com.sun.jdi.ObjectReference object () vrací objekt obsahující právě čtený či měněný atribut
3 com.sun.jdi.Value valueCurrent () vrací aktuální hodnotu sledovaného atributu
4 com.sun.jdi.ThreadReference thread () informace o threadu v monitorované JVM, v němž došlo ke čtení či zápisu do sledovaného atributu
5 com.sun.jdi.reqeuest.EventRequest request () objekt s jehož pomocí bylo nastaveno sledování tohoto typu události
6 com.sun.jdi.Location location () lokalizace kódu, který čte sledovaný atribut popř. do tohoto atributu zapisuje

4. Metody předepsané v rozhraní ModificationWatchpointEvent

V rozhraní com.sun.jdi.event.Modifica­tionWatchpointEvent je oproti rozhraním com.sun.jdi.event.WatchpointEventcom.sun.jdi.event.Acces­sWatchpointEvent předepsána jedna zvláštní metoda sloužící k přečtení nové hodnoty zapsané do sledovaného atributu:

# Návratový typ Jméno metody Parametry Význam
1 com.sun.jdi.Field field () vrací objekt představující sledovaný atribut
2 com.sun.jdi.ObjectReference object () vrací objekt obsahující právě čtený či měněný atribut
3 com.sun.jdi.Value valueCurrent () vrací aktuální hodnotu sledovaného atributu
4 com.sun.jdi.ThreadReference thread () informace o threadu v monitorované JVM, v němž došlo ke čtení či zápisu do sledovaného atributu
5 com.sun.jdi.reqeuest.EventRequest request () objekt s jehož pomocí bylo nastaveno sledování tohoto typu události
6 com.sun.jdi.Location location () lokalizace kódu, který čte sledovaný atribut popř. do tohoto atributu zapisuje
7 com.sun.jdi.Value valueToBe () nová hodnota zapisovaná do sledovaného atributu

5. Registrace události typu AccessWatchpointEvent

V následujícím textu budou postupně popsány ty nejdůležitější metody použité v dnešním demonstračním příkladu JDIWatchPointDemo3. Stejně jako v ostatních demonstračních příkladech, i zde se nejprve provede připojení k již běžícímu virtuálnímu stroji přes socket na portu 6502 a následně se zavolá uživatelská metoda acquireAndUseEventRequestManager, která provede registraci všech potřebných typů událostí a následný vstup do smyčky událostí (event loop):

    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
 
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
 
        // udalost volana pri pristupu k vybranemu atributu
        registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
 
        // udalost volana pri zapisu do vybranemu atributu
        registerModificationWatchpointRequest(virtualMachine, eventRequestManager);
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }

V metodě registerAccessWatchpointRequest(), kterou jsme si již ukázali i v předchozí části tohoto seriálu, se nejprve vyžádá generování událostí typu AccessWatchpointEvent a posléze se vytváření těchto typů událostí ještě musí explicitně povolit:

    /**
     * Registrace udalosti typu AccessWatchpointEvent
     * @param virtualMachine 
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void registerAccessWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        // ziskani objektu predstavujiciho vybrany atribut
        Field field = findField(virtualMachine);
 
        // registrace udalosti
        AccessWatchpointRequest accessWatchpointRequest = eventRequestManager.createAccessWatchpointRequest(field);
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        accessWatchpointRequest.enable();
    }

V této chvíli začnou být do fronty událostí (event queue) zapisovány i události typu AccessWatchpointEvent vyvolané ve chvíli, kdy se čte hodnota sledovaného atributu.

6. Registrace události typu ModificationWatchpointEvent

Dalším typem události, kterou musíme v demonstračním příkladu zpracovávat, jsou události typu ModificationWatchpointEvent, které jsou do fronty událostí vloženy ve chvíli, kdy se v monitorovaném virtuálním stroji provede zápis do sledovaného atributu. O registraci tohoto typu události se stará uživatelská metoda registerModificationWatchpo­intRequest():

    /**
     * Registrace udalosti typu ModificationWatchpointEvent
     * @param virtualMachine 
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void registerModificationWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        // ziskani objektu predstavujiciho vybrany atribut
        Field field = findField(virtualMachine);
 
        // registrace udalosti
        ModificationWatchpointRequest modificationWatchpointRequest = eventRequestManager.createModificationWatchpointRequest(field);
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        modificationWatchpointRequest.enable();
    }

7. Smyčka událostí se zpracováním jednotlivých typů událostí

Implementace smyčky událostí je zcela stejná jako smyčka použitá v demonstračních příkladech JDIWatchpointDemo1 a JDIWatchpointDemo2 popsaných minule:

    /**
     * Klasicka smycka pro postupne zpracovani udalosti.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }

Vlastní čtení událostí z fronty řeší uživatelská metoda processEvent(), v níž se zpracovávají čtyři typy událostí – AccessWatchpointEvent, ModificationWatchpointEvent, VMStartEvent a VMDeathEvent. V této metodě je nutné jednotlivé události rozpoznávat s využitím operátoru instanceof (druhou možností by bylo porovnání názvů tříd, to však není příliš efektivní):

    /**
     * Precteni a zpracovani udalosti
     * 
     * @param eventQueue
     *            fronta udalosti
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     * 
     * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud
     *         se ma sledovana VM ukoncit
     */
    private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) {
        EventSet eventSet;
        try {
            // precist udalosti z fronty
            eventSet = eventQueue.remove();
            int events = eventSet.size();
            System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":"));
 
            // projit vsemi udalostmi
            for (Event event : eventSet) {
                if (event instanceof VMStartEvent) {
                    System.out.println("    VMStartEvent");
                }
                else if (event instanceof VMDeathEvent) {
                    System.out.println("    VMDeathEvent");
                    // zakazat dalsi generovani udalosti
                    // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) 
                    vmDeathRequest.disable();
                    // posleze se zavola shutdownVirtualMachine()
                    return false;
                }
                else if (event instanceof AccessWatchpointEvent) {
                    // podrobnejsi vypis o pristupu k atributu
                    printAccessWatchpointInfo((AccessWatchpointEvent)event);
                }
                else if (event instanceof ModificationWatchpointEvent) {
                    // podrobnejsi vypis o zapisu do atributu
                    printModificationWatchpointInfo((ModificationWatchpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

8. Výpis zprávy v případě detekce události čtení sledovaného atributu

Konečně se dostáváme k metodám našeho demonstračního příkladu, které jsou zavolány ve chvíli čtení popř. zápisu do sledovaného atributu. První metodou je printAccessWatchpointInfo(), která vytiskne hodnotu atributu ve chvíli jeho čtení nějakým vláknem ve sledovaném virtuálním stroji Javy. Oproti podobné metodě popsané minule se však zde hodnota atributu získává poněkud odlišným způsobem. Připomeňme si, že minule jsme pro přečtení hodnoty celočíselného atributu používali zvláštní metodu nazvanou getFieldValue(), která vypadala následovně:

    /**
     * Ziskani hodnoty vybraneho atributu.
     *
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static int getFieldValue(AccessWatchpointEvent event) {
        ObjectReference objectReference = event.object();
        // dopredu zname typ atributu, tudiz lze provest pretypovani
        IntegerValue value = (IntegerValue)objectReference.getValue(event.field());
        return value.intValue();
    }

Dnes naproti tomu pro ilustraci poměrně velké variability rozhraní JDI použijeme jinou formu této metody, v níž se využije metoda WatchPointEvent.valueCurrent():

    /**
     * Ziskani hodnoty vybraneho (sledovaneho) atributu.
     *
     * @param event
     *            udalost typu WatchpointEvent
     * @return hodnota, ktera je ulozena prirazena ve sledovanem atributu
     *         prevedena na retezec
     */
    private static String getFieldValue(WatchpointEvent event) {
        return event.valueCurrent().toString();
    }

Při vzniku události typu AccessWatchpointEvent se získá jméno a aktuální hodnota sledovaného atributu a posléze se tyto hodnoty vypíšou na standardní výstup. Za těmito hodnotami se navíc ještě vypíšou další informace s využitím uživatelské metody printOtherWatchPointInfo() popsané v dalším textu:

    /**
     * Zprava vypsana ve chvili, kdy se cte hodnota sledovaneho atributu.
     *
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        String fieldValue = getFieldValue(event);
        String fieldName = event.field().name();
        System.out.format("    read value %s from attribute %s ", fieldValue, fieldName);
        printOtherWatchPointInfo(event);
    }

9. Výpis zprávy v případě detekce události zápisu do sledovaného atributu

Další uživatelská metoda printModificationWatchpointInfo() je zavolána tehdy, pokud ve sledovaném virtuálním stroji Javy dojde k zápisu do sledovaného atributu:

    /**
     * Zprava vypsana ve chvili, kdy se zapisuje nova hodnota do sledovaneho atributu.
     *
     * @param event
     *            udalost typu ModificationWatchpointEvent
     */
    private static void printModificationWatchpointInfo(ModificationWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        String fieldValue = getFieldValue(event);
        String fieldName = event.field().name();
        String newValue = getNewValue(event);
        System.out.format("    overwrite value %s in attribute %s by new value %s", fieldValue, fieldName, newValue);
        printOtherWatchPointInfo(event);
    }

Od uživatelské metody printAccessWatchpointInfo() se metoda printModificationWatchpointInfo() liší pouze tím, že přibyla informace o nové hodnotě atributu, což je informace získaná z další pomocné uživatelské metody nazvané getNewValue():

    /**
     * Precteni nove hodnoty, ktera ma byt prirazena do sledovaneho atributu.
     *
     * @param event
     *            udalost typu ModificationWatchpointEvent
     * @return nova hodnota, ktera ma byt prirazena do sledovaneho atributu
     *         prevedena na retezec
     */
    private static String getNewValue(ModificationWatchpointEvent event) {
        return event.valueToBe().toString();
    }

10. Informace o lokaci ve zdrojovém kódu, kde došlo ke čtení či k zápisu do sledovaného atributu

V uživatelských metodách printAccessWatchpointInfo()printModificationWatchpointInfo() je nutné mj. vypsat i informaci o lokaci ve zdrojovém kódu, v němž došlo ke čtení či k zápisu do sledovaného atributu. O tuto společnou funkci se stará metoda printOtherWatchPointInfo(), která nejprve z objektu typu WatchpointEvent získá instanci třídy Location a posléze z této instance přečte jméno třídy i metody, název zdrojového kódu, v němž byla třída deklarována a konečně i číslo řádku, na němž došlo ke čtení či zápisu do sledovaného atributu (poslední zmiňovaná informace se však vypíše pouze tehdy, pokud je testovací třída přeložena s přepínačem -g):

    /**
     * Vypis lokace v kodu, kde dochazi ke cteni ci k zapisu nove hodnoty
     * do sledovaneho atributu.
     *
     * @param event
     *            udalost typu WatchpointEvent
     */
    private static void printOtherWatchPointInfo(WatchpointEvent event) {
        // ziskat objekt typu Location, ktery obsahuje informaci o tom,
        // ve kterem miste kodu se k atributu pristupovalo
        Location location = event.location();
 
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // nyni jiz mame vsechny dulezite informace,
        // lze je tedy vypsat
        System.out.format("  at: %s.%s ", className, methodName);
        // po vypisu zakladnich informaci se jeste vypise jmeno zdrojoveho
        // souboru a cislo radku metody, v niz se k atributu pristupuje
        System.out.format("(%s:%s)\n", sourceName, lineNumber);
    }

11. Zdrojový kód demonstračního příkladu JDIWatchPointDemo3

Celý dnešní demonstrační příklad JDIWatchPointDemo3 pracuje tak, že se nejdříve zaregistrují všechny typy zpracovávaných událostí a posléze se spustí smyčka událostí. Vytváření všech sledovaných typů událostí nastane ve chvíli, kdy aplikace běžící v monitorovaném (cílovém) virtuálním stroji Javy přečte (v libovolném vláknu) atribut s plným jménem Test6.testedField, popř. modifikuje hodnotu tohoto atributu. Ve chvíli, kdy k přečtení tohoto atributu dojde, může demonstrační příklad ze své fronty událostí získat objekt typu AccessWatchpointEvent a vypsat prozatím základní informaci o této události na standardní výstup. Podobně je tomu i při zápisu do atributu, tj. při zpracování události typu ModificationWatchpointEvent. Následuje výpis celého zdrojového kódu dnešního prvního demonstračního příkladu:

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.Field;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.AccessWatchpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ModificationWatchpointEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.event.WatchpointEvent;
import com.sun.jdi.request.AccessWatchpointRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ModificationWatchpointRequest;
import com.sun.jdi.request.VMDeathRequest;
 
 
 
/**
 * Demonstracni priklad JDIWatchPointDemo3, v nemz je ukazan zpusob
 * vypisu zprav pri cteni i pri zapisu do sledovaneho atributu vybrane tridy.
 *
 * Pripojeni k bezicimu virtualnimu stroji Javy,
 * ktery byl spusten s parametry:
 * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida
 *
 * @author Pavel Tisnovsky
 */
public class JDIWatchPointDemo3 {
 
    /**
     * Navratovy kod pouzity pri ukoncovani sledovane JVM.
     */
    private static final int EXIT_VALUE = 0;
 
    /**
     * Jmeno konektoru, ktery pro pripojeni pouziva sockety.
     */
    private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach";
 
    /**
     * Jmeno testovane tridy.
     */
    private static final String TEST_CLASS_NAME = "Test6";
 
    /**
     * Jmeno sledovaneho atributu v testovane tride.
     */
    private static final String TESTED_FIELD_NAME = "testedField";
 
    /**
     * Vstupni metoda debuggeru.
     */
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM
        List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors();
 
        // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety
        AttachingConnector connector = getSocketAttachConnector(connectors);
 
        if (connector == null) {
            System.out.println("Socket connector is not available");
            return;
        }
 
        // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni
        debugVirtualMachineUsingSocket(connector);
    }
 
    /**
     * Ziskat konektor pouzivajici pro pripojeni sockety
     */
    private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) {
        for (AttachingConnector connector : connectors) {
            if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) {
                return connector;
            }
        }
        // nenasli jsme zadny vhodny konektor
        return null;
    }
 
    /**
     * Pripojeni k bezicimu virtualnimu stroji pres socket.
     * @throws InterruptedException 
     */
    private static void debugVirtualMachineUsingSocket(AttachingConnector connector) {
        // nastaveni argumentu pouzivanych konektorem
        Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector);
 
        try {
            // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy
            VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments);
 
            // spustit sledovany virtualni stroj Javy
            runVirtualMachine(virtualMachine);
 
            // ukazka pouziti EventRequestManageru
            acquireAndUseEventRequestManager(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni.
     *
     * @param connector konektor pouzity pro pripojeni
     * @return mapa obsahujici parametry konektoru
     */
    private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) {
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("port").setValue("6502");
        return arguments;
    }
 
    /**
     * Pripojeni debuggeru ke sledovanemu virtualnimu stroji.
     *
     * @param connector konektor vyuzivajici pro spojeni sockety
     * @param arguments mapa obsahujici parametry pripojeni
     * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     *
     * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari
     * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru
     */
    private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments)
        throws IOException, IllegalConnectorArgumentsException {
        System.out.println("Connecting to virtual machine");
        VirtualMachine virtualMachine = connector.attach(arguments);
        System.out.println("Connected");
        return virtualMachine;
    }
 
    /**
     * Spustit sledovany virtualni stroj
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachine(VirtualMachine virtualMachine) {
        virtualMachine.resume();
    }
 
    /**
     * Ukonceni prace beziciho sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void shutdownVirtualMachine(VirtualMachine virtualMachine) {
        System.out.println("Calling exit");
        virtualMachine.exit(EXIT_VALUE);
    }
 
    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
 
        // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
 
        // udalost volana pri pristupu k vybranemu atributu
        registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
 
        // udalost volana pri zapisu do vybranemu atributu
        registerModificationWatchpointRequest(virtualMachine, eventRequestManager);
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }
 
    /**
     * Registrace udalosti typu VMDeathEvent
     */
    private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) {
        VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest();
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        vmDeathRequest.enable();
        return vmDeathRequest;
    }
 
    /**
     * Registrace udalosti typu AccessWatchpointEvent
     * @param virtualMachine 
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void registerAccessWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        // ziskani objektu predstavujiciho vybrany atribut
        Field field = findField(virtualMachine);
 
        // registrace udalosti
        AccessWatchpointRequest accessWatchpointRequest = eventRequestManager.createAccessWatchpointRequest(field);
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        accessWatchpointRequest.enable();
    }
 
    /**
     * Registrace udalosti typu ModificationWatchpointEvent
     * @param virtualMachine 
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     */
    private static void registerModificationWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
        // ziskani objektu predstavujiciho vybrany atribut
        Field field = findField(virtualMachine);
 
        // registrace udalosti
        ModificationWatchpointRequest modificationWatchpointRequest = eventRequestManager.createModificationWatchpointRequest(field);
 
        // po registraci udalosti je jeste nutne tento typ udalosti povolit
        modificationWatchpointRequest.enable();
    }
 
    /**
     * Ziskani objektu predstavujiciho vybrany atribut ve sledovane VM.
     *
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @return nalezeny atribut
     */
    private static Field findField(VirtualMachine virtualMachine) {
        // mela by se vratit jedina trida
        List<ReferenceType> allClasses = virtualMachine.classesByName(TEST_CLASS_NAME);
        System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size());
 
        // vratila se jedina trida, takze ji precteme
        ReferenceType class1 = allClasses.get(0);
 
        // ziskani atributu z teto tridy
        return class1.fieldByName(TESTED_FIELD_NAME);
    }
 
    /**
     * Klasicka smycka pro postupne zpracovani udalosti.
     * 
     * @param virtualMachine
     *            sledovany virtualni stroj, k nemuz je debugger vzdalene
     *            pripojen
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }
 
    /**
     * Precteni a zpracovani udalosti
     * 
     * @param eventQueue
     *            fronta udalosti
     * @param vmDeathRequest
     *            objekt pro rizeni udalosti typu VMDeathEvent
     * 
     * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud
     *         se ma sledovana VM ukoncit
     */
    private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) {
        EventSet eventSet;
        try {
            // precist udalosti z fronty
            eventSet = eventQueue.remove();
            int events = eventSet.size();
            System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":"));
 
            // projit vsemi udalostmi
            for (Event event : eventSet) {
                if (event instanceof VMStartEvent) {
                    System.out.println("    VMStartEvent");
                }
                else if (event instanceof VMDeathEvent) {
                    System.out.println("    VMDeathEvent");
                    // zakazat dalsi generovani udalosti
                    // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) 
                    vmDeathRequest.disable();
                    // posleze se zavola shutdownVirtualMachine()
                    return false;
                }
                else if (event instanceof AccessWatchpointEvent) {
                    // podrobnejsi vypis o pristupu k atributu
                    printAccessWatchpointInfo((AccessWatchpointEvent)event);
                }
                else if (event instanceof ModificationWatchpointEvent) {
                    // podrobnejsi vypis o zapisu do atributu
                    printModificationWatchpointInfo((ModificationWatchpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Zprava vypsana ve chvili, kdy se cte hodnota sledovaneho atributu.
     *
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        String fieldValue = getFieldValue(event);
        String fieldName = event.field().name();
        System.out.format("    read value %s from attribute %s ", fieldValue, fieldName);
        printOtherWatchPointInfo(event);
    }
 
    /**
     * Zprava vypsana ve chvili, kdy se zapisuje nova hodnota do sledovaneho atributu.
     *
     * @param event
     *            udalost typu ModificationWatchpointEvent
     */
    private static void printModificationWatchpointInfo(ModificationWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        String fieldValue = getFieldValue(event);
        String fieldName = event.field().name();
        String newValue = getNewValue(event);
        System.out.format("    overwrite value %s in attribute %s by new value %s", fieldValue, fieldName, newValue);
        printOtherWatchPointInfo(event);
    }
 
    /**
     * Vypis lokace v kodu, kde dochazi ke cteni ci k zapisu nove hodnoty
     * do sledovaneho atributu.
     *
     * @param event
     *            udalost typu WatchpointEvent
     */
    private static void printOtherWatchPointInfo(WatchpointEvent event) {
        // ziskat objekt typu Location, ktery obsahuje informaci o tom,
        // ve kterem miste kodu se k atributu pristupovalo
        Location location = event.location();
 
        // ziskat vsechny informace o pozici v pozastavenem vlaknu
        String className = getClassName(location);
        String methodName = getMethodName(location);
        String sourceName = getSourceName(location);
        String lineNumber = getLineNumber(location);
 
        // nyni jiz mame vsechny dulezite informace,
        // lze je tedy vypsat
        System.out.format("  at: %s.%s ", className, methodName);
        // po vypisu zakladnich informaci se jeste vypise jmeno zdrojoveho
        // souboru a cislo radku metody, v niz se k atributu pristupuje
        System.out.format("(%s:%s)\n", sourceName, lineNumber);
    }
 
    /**
     * Ziskani hodnoty vybraneho (sledovaneho) atributu.
     *
     * @param event
     *            udalost typu WatchpointEvent
     * @return hodnota, ktera je ulozena prirazena ve sledovanem atributu
     *         prevedena na retezec
     */
    private static String getFieldValue(WatchpointEvent event) {
        return event.valueCurrent().toString();
    }
 
    /**
     * Precteni nove hodnoty, ktera ma byt prirazena do sledovaneho atributu.
     *
     * @param event
     *            udalost typu ModificationWatchpointEvent
     * @return nova hodnota, ktera ma byt prirazena do sledovaneho atributu
     *         prevedena na retezec
     */
    private static String getNewValue(ModificationWatchpointEvent event) {
        return event.valueToBe().toString();
    }
 
    /**
     * Jmeno tridy, jejiz metoda byla zavolana.
     */
    private static String getClassName(Location location) {
        return location.method().declaringType().name();
    }
 
    /**
     * Jmeno volane metody.
     */
    private static String getMethodName(Location location) {
        return location.method().name();
    }
 
    /**
     * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci.
     */
    private static String getSourceName(Location location) {
        try {
            return location.sourceName();
        }
        catch (AbsentInformationException e) {
            return "unknown";
        }
    }
 
    /**
     * Prevod cisla radku na retezec, pokud je to mozne.
     */
    private static String getLineNumber(Location location) {
        int lineNumber = location.lineNumber();
        // u nativnich metod nelze zjistit cisla radku
        return lineNumber >= 0 ? "" + lineNumber : "<native method>";
    }
 
}

12. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3

Podívejme se nyní na způsob přeložení a následného použití demonstračního příkladu JDIWatchPointDemo3. Překlad se provede následujícím skriptem (který předpokládá instalaci OpenJDK 6 do nastaveného adresáře):

javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIWatchpointDemo3.java

Před spuštěním tohoto příkladu je nutné z jiného terminálu spustit i testovací aplikaci Test6 (její zdrojový kód jsme si ukazovali minule):

java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test6

Spuštění dnešního demonstračního příkladu zajistí následující příkaz:

java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIWatchpointDemo3

Jakmile se testovací aplikace Test6 spustí, otevře její virtuální stroj Javy port číslo 6502, na němž se očekává připojení debuggeru. Následně tato aplikace počká na stisk libovolné klávesy. Po připojení debuggeru (resp. našeho demonstračního příkladu JDIWatchpointDemo3) by se na standardní výstup měly vypsat následující zprávy:

Connecting to virtual machine
Connected
Number of classes with name 'Test6': 1
Number of classes with name 'Test6': 1
Got 1 request:
    overwrite value 0 in attribute testedField by new value 1  at: Test6. (Test6.java:3)
Got 1 request:
    read value 1 from attribute testedField   at: Test6.run (Test6.java:6)
Got 1 request:
    overwrite value 1 in attribute testedField by new value 42  at: Test6.run (Test6.java:6)
Got 1 request:
    overwrite value 42 in attribute testedField by new value 1  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 1 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 1 in attribute testedField by new value 2  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 2 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 2 in attribute testedField by new value 4  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 4 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 4 in attribute testedField by new value 8  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 8 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 8 in attribute testedField by new value 16  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 16 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 16 in attribute testedField by new value 32  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 32 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 32 in attribute testedField by new value 64  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 64 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 64 in attribute testedField by new value 128  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 128 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 128 in attribute testedField by new value 256  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 256 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 1 request:
    overwrite value 256 in attribute testedField by new value 512  at: Test6.foo (Test6.java:12)
Got 1 request:
    read value 512 from attribute testedField   at: Test6.bar (Test6.java:18)
Got 2 requests:
    VMDeathEvent
Calling exit

Povšimněte si zejména způsobu výpisu zpráv v případě, že se na jednom programovém řádku čte aktuální hodnota atributu a současně se zapisuje hodnota nová. Jedná se především o řádek Test6.java:6, který vypadá následovně:

this.testedField *= value;

Podobný význam (současné čtení i zápis nové hodnoty do sledovaného atributu) by měl například i podobný řádek:

this.testedField ++;

13. Testovací třída Test7 – zápis shodné hodnoty, operátor ++ apod.

Zkusme si nyní dnešní demonstrační příklad JDIWatchpointDemo3 vyzkoušet spustit proti nové testovací třídě Test7.java, kde se atribut čte či zapisuje v trojici metod. Povšimněte si, že v první metodě foo() se stále zapisuje stejná konstanta, ve druhé metodě bar() se přičítá nula (tudíž se hodnota atributu nemění, ovšem zápis se provádí) a ve třetí metodě baz() se používá operátor ++ pro čtení i zápis:

widgety

public class Test7 {
 
    int testedField = 1;
 
    public void run() {
        foo();
        bar();
        baz();
    }
 
    public void foo() {
        for (int i = 1; i < 10; i++) {
            this.testedField = 0;
        }
    }
 
    public void bar() {
        for (int i = 1; i < 10; i++) {
            this.testedField += 0;
        }
    }
 
    public void baz() {
        for (int i = 1; i < 10; i++) {
            this.testedField++;
        }
    }
 
    public static void main(String[] args) {
        System.out.println("Press any key");
        try {
            System.in.read();
        }
        catch (java.io.IOException e) {
            // . //
        }
        new Test7().run();
    }
 
}

14. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3 pro třídu Test7

Podívejme se nyní na výstup příkladu JDIWatchPointDemo3 ve chvíli, kdy je spuštěn oproti třídě Test7 v monitorovaném virtuálním stroji Javy:

Connecting to virtual machine
Connected
Number of classes with name 'Test7': 1
Test7.testedField
Number of classes with name 'Test7': 1
Test7.testedField
Got 1 request:
    overwrite value 0 in attribute testedField by new value 1  at: Test7. (Test7.java:3)
Got 1 request:
    overwrite value 1 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.foo (Test7.java:13)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.bar (Test7.java:19)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 0  at: Test7.bar (Test7.java:19)
Got 1 request:
    read value 0 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 0 in attribute testedField by new value 1  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 1 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 1 in attribute testedField by new value 2  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 2 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 2 in attribute testedField by new value 3  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 3 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 3 in attribute testedField by new value 4  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 4 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 4 in attribute testedField by new value 5  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 5 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 5 in attribute testedField by new value 6  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 6 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 6 in attribute testedField by new value 7  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 7 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 7 in attribute testedField by new value 8  at: Test7.baz (Test7.java:25)
Got 1 request:
    read value 8 from attribute testedField   at: Test7.baz (Test7.java:25)
Got 1 request:
    overwrite value 8 in attribute testedField by new value 9  at: Test7.baz (Test7.java:25)
Got 2 requests:
    VMDeathEvent
Calling exit

15. Repositář se zdrojovými kódy demonstračního příkladu

Zdrojové kódy demonstračního příkladu JDIWatchPointDemo3.java jsou společně s dalšími pomocnými soubory uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:

16. Odkazy na Internetu

  1. Class com.sun.jdi.Bootstrap
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­package-tree.html
  2. Interface com.sun.jdi.VirtualMachine
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­VirtualMachine.html
  3. Interface com.sun.jdi.Field
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­Field.html
  4. Interface com.sun.jdi.ReferenceType
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­ReferenceType.html
  5. Interface com.sun.jdi.TypeComponent
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­TypeComponent.html
  6. Interface com.sun.jdi.Accessible
    http://docs.oracle.com/ja­vase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/­Accessible.html
  7. Breakpoint (Wikipedia)
    http://cs.wikipedia.org/wi­ki/Breakpoint
  8. JVM Tool Interface Version 1.2 Documentation
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  9. JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#SetBreakpoint
  10. JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#ClearBreakpoint
  11. JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#Breakpoint
  12. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  13. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  14. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  15. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  16. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  17. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  18. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  19. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  20. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  21. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  22. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  23. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  24. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  25. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
Našli jste v článku chybu?
120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?

DigiZone.cz: Digi2GO u Alza.cz a s balíčkem Sport zdarma

Digi2GO u Alza.cz a s balíčkem Sport zdarma

Vitalia.cz: Jaký je rozdíl mezi brambůrky a chipsy?

Jaký je rozdíl mezi brambůrky a chipsy?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

Podnikatel.cz: Dva měsíce na EET. Budou stačit?

Dva měsíce na EET. Budou stačit?

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Podnikatel.cz: EET pro e-shopy? Postavené na hlavu

EET pro e-shopy? Postavené na hlavu

Lupa.cz: Aukro.cz mění majitele. Vrací se do českých rukou

Aukro.cz mění majitele. Vrací se do českých rukou

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

Podnikatel.cz: Kalousek chce odklad EET. Předvolební tah?

Kalousek chce odklad EET. Předvolební tah?

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

Vitalia.cz: Když všichni seli řepku, on vsadil na dýně

Když všichni seli řepku, on vsadil na dýně

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“