Hlavní navigace

Pohled pod kapotu JVM – detekce čtení hodnoty vybraného atributu s využitím rozhraní JDI

28. 5. 2013
Doba čtení: 26 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si na dvojici demonstračních příkladů ukážeme další možnosti nabízené rozhraním JDI – bude se jednat o jednoduché monitorovací nástroje, které dokážou zaregistrovat přístup (čtení) ke zvolenému atributu vybrané třídy.

Obsah

1. Pohled pod kapotu JVM – detekce čtení hodnoty vybraného atributu s využitím rozhraní JDI

2. Registrace události vyvolané při čtení vybraného atributu

3. Získání objektu typu Field představujícího sledovaný atribut

4. Smyčka událostí a výpis informace o události typu AccessWatchpointEvent

5. Zdrojový kód prvního demonstračního příkladu JDIWatchpointDemo1

6. Výstup generovaný prvním demonstračním příkladem

7. Výpis podrobnějších informací o watchpointu v metodě printAccessWatchpointInfo

8. Rozhraní ObjectReference a z něj odvozená hierarchie tříd a rozhraní

9. Získání hodnoty vybraného atributu v metodě getFieldValue

10. Zdrojový kód druhého demonstračního příkladu JDIWatchpointDemo2

11. Výstup generovaný druhým demonstračním příkladem

12. Zdrojový kód testovací třídy Test6

13. Repositář se zdrojovými kódy obou demonstračních příkladů

14. Odkazy na Internetu

1. Pohled pod kapotu JVM – detekce čtení hodnoty 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 se již potřetí budeme zabývat popisem systému událostí implementovaného v rozhraní JDI (Java Debugger Interface). V předchozích demonstračních příkladech bylo ukázáno, jak může jednoduchý debugger reagovat na událost typu VMDeathEvent (ukončení činnosti sledovaného virtuálního stroje) a taktéž na událost typu MethodEntryEvent, která vznikne ve chvíli, kdy některé vlákno v monitorované JVM vstoupí do metody (dojde tedy k zavolání nějaké metody). S využitím těchto dvou typů událostí lze poměrně snadno implementovat například profiler, popř. nástroj sledující, které metody jsou pokryty testy, popř.  naopak které metody se vůbec nevyužívají. Ovšem v případě, že bychom se snažili o vytvoření plnohodnotného debuggeru, je nutné začít využívat i další typy událostí.

Prakticky všechny moderní debuggery nabízí svým uživatelům možnost sledování hodnot vybraných atributů nebo vybraných lokálních proměnných. My si dnes ukážeme, jakým způsobem lze poměrně jednoduše monitorovat čtení hodnoty vybraného atributu (ať již atributu statického, tak i atributu objektu). Celý systém pracuje následujícím způsobem: s využitím rozhraní JDI se nejdříve získá objekt typu com.sun.jdi.Field představující sledovaný atribut ve vybrané třídě. Posléze se zaregistruje a povolí vytváření událostí typu AccessWatchpointEvent ve chvíli, kdy se nějaké vlákno v monitorovaném virtuálním stroji Javy pokusí o přečtení vybraného atributu. Jakmile tato událost vznikne, lze (opět přes rozhraní JDI) zjistit jak místo (třída+metoda+řádek zdrojového kódu) čtení atributu, tak i jeho hodnotu. Celý postup si vysvětlíme na dvojici demonstračních příkladů JDIWatchpointDemo1 a JDIWatchpointDemo2.

2. Registrace události vyvolané při čtení vybraného atributu

V dnešních dvou demonstračních příkladech budou využity dva typy událostí. Kromě události typu VMDeathEvent, kterou potřebujeme pro korektní ukončení debuggeru budeme ještě zpracovávat událost typu AccessWatchpointEvent. Registrace obou zmíněných typů událostí se provádí v uživatelské metodě nazvané acquireAndUseEventRequestManager(), která nejprve zaregistruje oba typy událostí a posléze zavolá klasickou smyčku událostí implementovanou v samostatné metodě:

    /**
     * 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
        AccessWatchpointRequest accessWatchPointRequest = registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, accessWatchPointRequest);
    }

V metodě registerAccessWatchpointRequest() 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 AccessWatchpointRequest 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();
        return accessWatchpointRequest;
    }

3. Získání objektu typu Field představujícího sledovaný atribut

Jak jste si zajisté v předchozí kapitole všimli, volá se v metodě registerAccessWatchpointRequest() další uživatelská metoda nazvaná findField(). Úkolem této metody je vytvořit objekt typu com.sun.jdi.Field, který bude představovat sledovaný atribut. Podívejme se nejdříve na to, jak metoda findField() vlastně vypadá:

    /**
     * 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());
        ReferenceType class1 = allClasses.get(0);
        // ziskani atributu
        return class1.fieldByName(TESTED_FIELD_NAME);
    }

Nejdříve se přes VirtualMachine.classesByName() získá seznam všech tříd, jejichž jméno odpovídá řetězci „Test6“. Vzhledem k tomu, že tato třída existuje pouze jedna, můžeme z vráceného seznamu jednoduše přečíst první prvek a následně s využitím volání ReferenceType.fieldByName() získat kýžený objekt typu com.sun.jdi.Field.

4. Smyčka událostí a výpis informace o události typu AccessWatchpointEvent

Smyčka událostí implementovaná v demonstračním příkladu JDIWatchpointDemo1 je téměř totožná se smyčkou událostí, kterou jsme si ukazovali již minule a předminule. V uživatelské metodě eventLoop() se čeká na událost typu VMDeathEvent, přičemž se pro všechny události volá další uživatelská metoda processEvents:

    /**
     * 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
     * @param accessWatchPointRequest
     *            objekt pro rizeni udalosti typu AccessWatchpointEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, AccessWatchpointRequest accessWatchPointRequest) {
        EventQueue eventQueue = virtualMachine.eventQueue();
 
        // precist a zpracovat udalosti
        while (processEvents(eventQueue, vmDeathRequest)) {
            // jojo tady skutecne nic neni :)
        }
    }

Uživatelská metoda processEvents() vrací pravdivostní hodnotu true v případě, že se má pokračovat ve čtení a zpracování dalších událostí a hodnotu false ve chvíli, kdy přišla událost typu VMDeathEvent a kdy má tedy debugger (demonstrační příklad) ukončit svoji činnost:

    /**
     * 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) {
                    printAccessWatchpointInfo((AccessWatchpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

Ve chvíli, kdy nastane čtení sledovaného atributu, dojde k vytvoření události AccessWatchpointEvent, na což se v demonstračním příkladu reaguje zavoláním uživatelské metody printAccessWatchpointInfo(), která je prozatím velmi jednoduchá, protože pouze vypíše základní informace o získaném objektu typu AccessWatchpointEvent:

    /**
     * Zavolano pri vyskytu udalosti typu AccessWatchpointEvent
     * 
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        System.out.println("    " + event.toString());
    }

5. Zdrojový kód prvního demonstračního příkladu JDIWatchpointDemo1

V dnešním prvním demonstračním příkladu je nejdříve s využitím objektu typu AccessWatchpointRequest vyžádáno a následně i povoleno generování událostí typu AccessWatchpointEvent. Vytváření těchto 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. 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 jen velmi základní informaci o této události na standardní výstup. 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.Bootstrap;
import com.sun.jdi.Field;
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.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.AccessWatchpointRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.VMDeathRequest;
 
 
 
/**
 * 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 JDIWatchpointDemo1 {
 
    /**
     * 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
        AccessWatchpointRequest accessWatchPointRequest = registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, accessWatchPointRequest);
    }
 
    /**
     * 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 AccessWatchpointRequest 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();
        return accessWatchpointRequest;
    }
 
    /**
     * 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());
        ReferenceType class1 = allClasses.get(0);
        // ziskani atributu
        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
     * @param accessWatchPointRequest
     *            objekt pro rizeni udalosti typu AccessWatchpointEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, AccessWatchpointRequest accessWatchPointRequest) {
        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) {
                    printAccessWatchpointInfo((AccessWatchpointEvent)event);
                }
                else {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    /**
     * Zavolano pri vyskytu udalosti typu AccessWatchpointEvent
     * 
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        System.out.println("    " + event.toString());
    }
 
}

6. Výstup generovaný prvním demonstračním příkladem

První demonstrační příklad JDIWatchpointDemo1 se přeloží podobným způsobem, jako i všechny další demonstrační příklady využívající rozhraní JDI. V případě využití OpenJDK6 lze překlad provést následovně:

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

Následně je nutné v dalším terminálu spustit sledovanou aplikaci:

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

A posléze demonstrační „debugger“ spustit:

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

Po spuštění by měl debugger na svůj standardní výstup vypsat následující informace:

Connecting to virtual machine
Connected
Number of classes with name 'Test6': 1
Got 1 request:
    AccessWatchpoint@Test6:6 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 1 request:
    AccessWatchpoint@Test6:18 in thread main
Got 2 requests:
    VMDeathEvent
Calling exit

7. Výpis podrobnějších informací o watchpointu v metodě printAccessWatchpointInfo

Samotné zjištění faktu, že se ve sledovaném virtuálním stroji Javy přečetl monitorovaný atribut, je sice možná zajímavé, ale ve skutečném debuggeru samozřejmě také potřebujeme znát podrobnější informace. Proto uživatelskou metodu printAccessWatchpointInfo() upravíme takovým způsobem, aby bylo možné tyto informace získat. Většinu funkcí použitých v této metodě již známe, protože jsme se s nimi setkali minule – jedná se o zjištění lokace ve zdrojovém textu, kde došlo ke čtení sledovaného atributu. Z objektu typu Location lze relativně snadno získat jméno třídy i jméno příslušné metody, jakož i číslo řádku a popř. i jméno zdrojového souboru. Dále pak budeme potřebovat přečíst aktuální hodnotu sledovaného atributu, což nám zajistí uživatelská metoda getFieldValue() popsaná v navazujících dvou kapitolách:

    /**
     * Zavolano pri vyskytu udalosti typu AccessWatchpointEvent
     * 
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        int fieldValue = getFieldValue(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("    access to tested attribute with value %d in: %s.%s",
                fieldValue, 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);
    }

Uživatelské metody getClassName(), getMethodName(), getSourceName() a getLineNumber() si již podrobněji popisovat nebudeme, protože jsme se s nimi setkali minule i předminule.

8. Rozhraní ObjectReference a z něj odvozená hierarchie tříd a rozhraní

Nyní již máme poměrně přesné informace o kódu, v němž se provádí čtení sledovaného atributu, takže nám zbývá doplnit maličkost – zjistit a následně vypsat hodnotu tohoto atributu. K tomuto účelu lze využít metodu object() předepsanou v rozhraní com.sun.jdi.event.WatchpointEvent (toto rozhraní je předkem dnes používaného rozhraní com.sun.jdi.event.AccessWat­chpointEvent). Metoda com.sun.jdi.event.Watchpo­intEvent.object() vrací informaci o sledovaném atributu, přičemž tato informace je typu com.sun.jdi.ObjectReference:

com.sun.jdi.ObjectReference com.sun.jdi.event.WatchpointEvent.object();

Ze samotného rozhraní ObjectReference jsou odvozena další rozhraní:

com.sun.jdi.ArrayReference
com.sun.jdi.ClassLoaderReference
com.sun.jdi.ClassObjectReference
com.sun.jdi.StringReference
com.sun.jdi.ThreadGroupReference
com.sun.jdi.ThreadReference

Nás dnes bude zajímat především metoda:

com.sun.jdi.Value getValue(com.sun.jdi.Field field)

vracející hodnotu uloženou do atributu, popř. do jiného sledovaného objektu. Samotné rozhraní com.sun.jdi.Value je opět rozšířeno v dalších rozhraních tvořících jednoduchou hierarchii:

PrimitiveValue
    BooleanValue
    ByteValue
    CharValue
    DoubleValue
    FloatValue
    IntegerValue
    LongValue
    ShortValue
 
ObjectReference
    ArrayReference
    ClassLoaderReference
    ClassObjectReference
    StringReference
    ThreadGroupReference
    ThreadReference
 
VoidValue

Každé z těchto rozhraní obsahuje předpis metody value() s různým návratovým typem. Smysl této metody je zřejmý – zjistit hodnotu sledovaného objektu, v našem případě atributu.

9. Získání hodnoty vybraného atributu v metodě getFieldValue

Nyní je již zřejmé, jakým způsobem lze získat hodnotu sledovaného atributu. Naše situace je vlastně velmi jednoduchá (na rozdíl od obecného debuggeru), protože víme, že atribut Test6.testedField je primitivního typu int, takže můžeme obecnou hodnotu typu ObjectReference přetypovat na IntegerValue. Uživatelská metoda getFieldValue() vracející aktuální hodnotu sledovaného atributu Test6.testedField tedy bude vypadat 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();
    }

10. Zdrojový kód druhého demonstračního příkladu JDIWatchpointDemo2

Vylepšení popsaná v předchozích třech kapitolách jsou součástí dnešního druhého demonstračního příkladu nazvaného jednoduše JDIWatchpointDemo2. Jakmile ve sledované aplikaci dojde ke čtení atributu Test6.testedField, vypíše demonstrační příklad podrobné informace o místě (třída+metoda+číslo řádku+zdrojový soubor), kde ke čtení atributu došlo a navíc se vypíše i hodnota tohoto atributu:

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.IntegerValue;
import com.sun.jdi.Location;
import com.sun.jdi.ObjectReference;
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.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.request.AccessWatchpointRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.VMDeathRequest;
 
 
 
/**
 * 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 JDIWatchpointDemo2 {
 
    /**
     * 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
        AccessWatchpointRequest accessWatchPointRequest = registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
 
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest, accessWatchPointRequest);
    }
 
    /**
     * 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 AccessWatchpointRequest 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();
        return accessWatchpointRequest;
    }
 
    /**
     * 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
     * @param accessWatchPointRequest
     *            objekt pro rizeni udalosti typu AccessWatchpointEvent
     */
    private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest, AccessWatchpointRequest accessWatchPointRequest) {
        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
     */
    /**
     * Zavolano pri vyskytu udalosti typu AccessWatchpointEvent
     * 
     * @param event
     *            udalost typu AccessWatchpointEvent
     */
    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 {
                    System.out.println("    other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
    private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
        // ziskat aktualni hodnotu atributu
        int fieldValue = getFieldValue(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("    access to tested attribute with value %d in: %s.%s",
                fieldValue, 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 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();
    }
 
    /**
     * 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>";
    }
 
}

11. Výstup generovaný druhým demonstračním příkladem

Dnešní druhý demonstrační příklad se překládá stejným způsobem jako příklad první:

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

Pro jistotu si zopakujeme i spuštění sledované aplikace Test6:

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

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

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

Jak je z následujícího výpisu zpráv patrné, vypisuje druhý demonstrační příklad informace mnohem podrobnější, než příklad první:

Cloud 24 - tip 1

Connecting to virtual machine
Connected
Number of classes with name 'Test6': 1
Got 1 request:
    access to tested attribute with value 1 in: Test6.run   (Test6.java:6)
Got 1 request:
    access to tested attribute with value 1 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 2 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 4 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 8 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 16 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 32 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 64 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 128 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 256 in: Test6.bar   (Test6.java:18)
Got 1 request:
    access to tested attribute with value 512 in: Test6.bar   (Test6.java:18)
Got 2 requests:
    VMDeathEvent
Calling exit

12. Zdrojový kód testovací třídy Test6

Oba demonstrační příklady popsané v dnešním článku jsou odzkoušeny na testovací třídě nazvané Test6, která je spuštěna v monitorovaném virtuálním stroji Javy. Po spuštění této testovací třídy se nejdříve čeká na stisk libovolné klávesy a posléze se v metodách Test6.run, Test6.foo a Test6.bar přistupuje k hodnotě atributu Test6.testedField, což je detekováno oběma demonstračními příklady:

public class Test6 {
 
    int testedField = 1;
 
    public void run(int value) {
        this.testedField *= value;
        foo();
    }
 
    public void foo() {
        for (int i = 1; i < 1000; i *= 2) {
            this.testedField = i;
            bar();
        }
    }
 
    public void bar() {
        System.out.println(this.testedField);
    }
 
    public static void main(String[] args) {
        System.out.println("Press any key");
        try {
            System.in.read();
        }
        catch (java.io.IOException e) {
            // . //
        }
        new Test6().run(42);
    }
 
}

13. Repositář se zdrojovými kódy obou demonstračních příkladů

Zdrojové kódy obou dnes popsaných demonstračních příkladů byly uloženy (podobně jako tomu bylo i v předešlých částech tohoto seriálu) do Mercurial repositáře dostupného na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze zmíněných zdrojových souborů můžete najít na adresách:

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

Byl pro vás článek přínosný?