Hlavní navigace

Pohled pod kapotu JVM – využití systémů událostí při sledování činnosti JVM s využitím rozhraní JDI

Pavel Tišnovský

V dnešní části seriálu o programovacím jazyku Java si popíšeme využití systému událostí implementovaného v rozhraní JDI (Java Debugger Interface). Události – events – jsou totiž v JDI využívány při zkoumání mnoha činností prováděných v monitorovaném JVM, například při práci s breakpointy či při krokování programu.

Obsah

1. Pohled pod kapotu JVM – využití systémů událostí při sledování činnosti JVM s využitím rozhraní JDI

2. Rozhraní deklarovaná v balíčku com.sun.jdi.event

3. Obecné události vztažené k celému sledovanému virtuálnímu stroji

4. Události, které vznikají v konkrétních vláknech a na známém místě v aplikaci

5. Rozhraní deklarovaná v baličku com.sun.jdi.request

6. Získání objektu typu com.sun.jdi.request.EventRe­questManager

7. Registrace nového typu události a povolení generování událostí

8. Smyčka událostí implementovaná s využitím objektu typu com.sun.jdi.event.EventQueue

9. Zpracování jednotlivých typů událostí a reakce na ukončení práce sledované JVM

10. Kompletní zdrojový kód demonstračního příkladu JDIEventRequestManager

11. Repositář se zdrojovými kódy demonstračního příkladu JDIEventRequestManager i podpůrných skriptů

12. Odkazy na Internetu

1. Pohled pod kapotu JVM – využití systémů událostí při sledování činnosti JVM s využitím rozhraní JDI

Dnešní část seriálu o programovacím jazyku Java i o virtuálním stroji Javy je věnována velmi důležité části rozhraní JDI (Java Debugger Interface), bez níž by nebylo možné efektivně sledovat všechny činnosti prováděné ve sledované (cílové) JVM. Jedná se o systém událostí (events), přičemž termínem „událost“ je zde myšlena nějaká změna stavu sledovaného virtuálního stroje Javy, která se na straně debuggeru projeví vznikem objektu typu com.sun.jdi.event.Event. Debugger může tyto události postupně číst z takzvané fronty událostí (event queue) a nějakým způsobem na tyto události reagovat. Postupné čtení událostí je většinou implementováno formou programové smyčky (event loop), na jejímž začátku se přečte nová událost z fronty událostí a posléze se provede rozeskok na základě typu přečtené události. Debugger většinou musí reagovat minimálně na událost typu VMDeathEvent, která nastane ve chvíli, kdy je činnost sledovaného virtuálního stroje ukončována.

Jaké události je vlastně možné zpracovávat? Jedná se například o následující činnosti probíhající ve sledovaném virtuálním stroji:

  1. Start sledovaného virtuálního stroje
  2. Ukončení práce sledovaného virtuálního stroje
  3. Vytvoření a spuštění nového vlákna
  4. Ukončení činnosti vlákna
  5. Vstup vlákna do stavu čekání na jiné vlákno (monitor)
  6. Vstup na breakpoint v nějakém vlákně běžícím ve sledované JVM
  7. Čtení či zápis do sledovaného atributu (watchpoint)
  8. Provedení jednoho kroku při krokování programu ve sledované JVM
  9. Vznik výjimky
  10. Vstup do metody
  11. Výskok z metody

V následujících kapitolách si na demonstračním příkladu ukážeme, jakým způsobem je možné sestavit velmi jednoduchý debugger, který nejprve povolí příjem některých typů událostí a posléze tyto události bude zpracovávat ve smyčce událostí (event loop).

2. Rozhraní deklarovaná v balíčku com.sun.jdi.event

Události, které může debugger využívající rozhraní JDI zpracovávat, i některé další pomocné objektové typy, jsou součástí balíčku com.sun.jdi.event. Tento balíček ve skutečnosti neobsahuje žádné veřejné třídy, ale pouze sadu rozhraní, která jsou interně implementována způsobem, který může být závislý na verzi JDI. V první tabulce je vypsáno pět základních rozhraní, které můžeme v balíčku com.sun.jdi.event nalézt a taktéž využít:

# Rozhraní Popis
1 com.sun.jdi.event.EventQueue Představuje frontu událostí, z níž debugger postupně může události vybírat a dále s nimi pracovat
2 com.sun.jdi.event.EventSet Události nejsou z fronty vybírány jednotlivě, ale po skupinách sdružených v objektech implementujících rozhraní EventSet
3 com.sun.jdi.event.EventIterator Jednotlivými událostmi uloženými v EventSet lze procházet přes tento iterátor získaný metodou EventSet.eventIterator()
4 com.sun.jdi.event.Event Obecná libovolná událost (předek dalších rozhraní popsaných dále)
5 com.sun.jdi.event.LocatableEvent Obecná událost vztažená ke konkrétnímu vláknu a pozici v tomto vláknu

Fronta událostí, tj. objekt typu com.sun.jdi.event.EventQueue se získá velmi snadno ve chvíli, kdy debugger vytvořil objekt typu com.sun.jdi.VirtualMachine:

EventQueue eventQueue = virtualMachine.eventQueue();

Důležité je, že pro každý sledovaný virtuální stroj existuje pouze jedna fronta událostí, takže tato metoda vrátí vždy stejný objekt typu EventQueue. To znamená, že debugger většinou všechny události zpracovává v jediné části kódu a nemůže například získat dvě fronty pro různé typy událostí. Vzhledem k tomu, že se každý typ události typicky zpracovává zcela odlišným způsobem (například vstup na breakpoint versus detekce vzniku výjimky), nevyhneme se při implementaci debuggeru nutnosti implementovat nějaký typ rozeskoku.

3. Obecné události vztažené k celému sledovanému virtuálnímu stroji

V předchozí kapitole bylo naznačeno, že se jednotlivé události rozdělují 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. Nejprve se budeme zabývat první skupinou obecných událostí. Celkem existuje sedm typů těchto událostí a všechny tyto typy jsou vypsány v tabulce níže:

# 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

Poznámka: události typu VMStartEvent, VMDeathEvent a VMDisconnectEvent jsou generovány vždy, tj. i tehdy, pokud nebyly explicitně povoleny!

4. Události, které vznikají v konkrétních vláknech a na známém místě v aplikaci

Následující události jsou, na rozdíl od událostí popsaných výše, naopak vztaženy ke konkrétnímu vláknu a debugger může díky tomu přesně zjistit, ve které části kódu k události došlo (což je mnohdy velmi důležité, například v případě použití breakpointů):

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

5. Rozhraní deklarovaná v baličku com.sun.jdi.request

Kromě rozhraní z balíčku com.sun.jdi.event je nutné při práci s událostmi využívat i několik rozhraní, která najdeme v balíčku com.sun.jdi.request. Aby totiž debugger mohl ve smyčce událostí jednotlivé události získávat a dále zpracovávat, je nutné daný typ události nejprve povolit, a to právě přes zmíněná rozhraní. V balíčku com.sun.jdi.request nalezneme především tuto dvojici rozhraní:

# Rozhraní Popis
1 com.sun.jdi.request.EventRequest Předek všech rozhraní uvedených v další tabulce
2 com.sun.jdi.request.EventRequestManager Objekt (existující pro sledovanou JVM jako singleton), přes nějž lze požadovat generování určitého typu událostí

Objekt typu com.sun.jdi.request.EventRe­questManager lze získat chvíli, kdy debugger vytvořil objekt typu com.sun.jdi.VirtualMachine:

EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();

V předchozí tabulce bylo uvedeno, že rozhraní com.sun.jdi.request.EventRequest je předkem pro další rozhraní. Ta jsou uvedena nyní. Každé rozhraní z této tabulky odpovídá jednomu typu události:

# Rozhraní Popis
1 com.sun.jdi.request.BreakpointRequest Žádost o generování událostí typu com.sun.jdi.event.BreakpointEvent
2 com.sun.jdi.request.ClassPrepareRequest Žádost o generování událostí typu com.sun.jdi.event.ClassPrepareEvent
3 com.sun.jdi.request.ClassUnloadRequest Žádost o generování událostí typu com.sun.jdi.event.ClassUnloadEvent
4 com.sun.jdi.request.ExceptionRequest Žádost o generování událostí typu com.sun.jdi.event.ExceptionEvent
5 com.sun.jdi.request.MethodEntryRequest Žádost o generování událostí typu com.sun.jdi.event.MethodEntryEvent
6 com.sun.jdi.request.MethodExitRequest Žádost o generování událostí typu com.sun.jdi.event.MethodExitEvent
7 com.sun.jdi.request.Monitor­ContendedEnteredRequest Žádost o generování událostí typu com.sun.jdi.event.Monitor­ContendedEnteredEvent
8 com.sun.jdi.request.Monitor­ContendedEnterRequest Žádost o generování událostí typu com.sun.jdi.event.Monitor­ContendedEnterEvent
9 com.sun.jdi.request.Monitor­WaitedRequest Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitedEvent
10 com.sun.jdi.request.MonitorWaitRequest Žádost o generování událostí typu com.sun.jdi.event.MonitorWaitEvent
11 com.sun.jdi.request.StepRequest Žádost o generování událostí typu com.sun.jdi.event.StepEvent
12 com.sun.jdi.request.ThreadDeathRequest Žádost o generování událostí typu com.sun.jdi.event.ThreadDeathEvent
13 com.sun.jdi.request.ThreadStartRequest Žádost o generování událostí typu com.sun.jdi.event.ThreadStartEvent
14 com.sun.jdi.request.VMDeathRequest Žádost o generování událostí typu com.sun.jdi.event.VMDeathEvent
15 com.sun.jdi.request.WatchpointRequest Žádost o generování událostí typu com.sun.jdi.event.WatchpointEvent
16 com.sun.jdi.request.Acces­sWatchpointRequest Žádost o generování událostí typu com.sun.jdi.AccessWatchpointEvent
17 com.sun.jdi.request.Modifi­cationWatchpointRequest Žádost o generování událostí typu com.sun.jdi.event.Modifica­tionWatchpointEvent

Povšimněte si absence „povolovacích rozhraní“ pro události typu VMStartEvent a VMDisconnectEvent. Tyto události jsou generovány vždy, tudíž pro jejich řízení není zapotřebí žádných specializovaných objektů.

6. Získání objektu typu com.sun.jdi.request.EventRe­questManager

Konečně známe všechna rozhraní, s nimiž bude náš demonstrační debugger pracovat, takže si můžeme vysvětlit, jak je možné po připojení k cílové JVM povolit některé typy událostí a jak implementovat smyčku událostí. Dnešní demonstrační příklad je založen na příkladech vysvětlených v předchozích částech tohoto seriálu, což znamená, že už si nebudeme vysvětlovat způsob připojení k cílové JVM, ale hned si uvedeme zdrojový kód metody zavolaný po připojení k této JVM. V této metodě je nejprve získán objekt typu EventRequestManager, což je pro danou instanci třídy virtualMachine singleton. Dále se s využitím tohoto objektu zaregistruje a povolí události typu VMDeathEvent a ihned poté se vstoupí do smyčky událostí:

    /**
     * Ukazka pouziti EventRequestManageru.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) {
        EventRequestManager eventRequestManager = virtualMachine.eventRequestManager();
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager);
        // klasicka smycka pro zpracovani udalosti
        eventLoop(virtualMachine, vmDeathRequest);
    }

7. Registrace nového typu události a povolení generování událostí

Vlastní žádost o registraci události typu VMDeathEvent je velmi jednoduchá, protože využijeme metodu eventRequestManager.createV­MDeathRequest() vracející objekt typu VMDeathRequest (viz též kapitola číslo pět). Jakmile tento objekt získáme, je důležité povolit generování událostí:

    /**
     * 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;
    }

V rozhraní com.sun.jdi.request.EventRe­questManager jsou předepsány tyto metody určené pro získání objektů typu com.sun.jdi.request.*Request:

# Návratový typ Jméno metody Parametry
1 com.sun.jdi.request.Acces­sWatchpointRequest createAccessWatchpointRequest (Field field)
2 com.sun.jdi.request.BreakpointRequest createBreakpointRequest (Location location)
3 com.sun.jdi.request.ClassPrepareRequest createClassPrepareRequest ()
4 com.sun.jdi.request.ClassUnloadRequest createClassUnloadRequest ()
5 com.sun.jdi.request.ExceptionRequest createExceptionRequest (ReferenceType refType, boolean notifyCaught, boolean notifyUncaught)
6 com.sun.jdi.request.MethodEntryRequest createMethodEntryRequest ()
7 com.sun.jdi.request.MethodExitRequest createMethodExitRequest ()
8 com.sun.jdi.request.Modifi­cationWatchpointRequest createModificationWatchpointRequest (Field field)
9 com.sun.jdi.request.Monitor­ContendedEnteredRequest createMonitorContendedEnteredRequest ()
10 com.sun.jdi.request.Monitor­ContendedEnterRequest createMonitorContendedEnterRequest ()
11 com.sun.jdi.request.Monitor­WaitedRequest createMonitorWaitedRequest ()
12 com.sun.jdi.request.MonitorWaitRequest createMonitorWaitRequest ()
13 com.sun.jdi.request.StepRequest createStepRequest (ThreadReference thread, int size, int depth)
14 com.sun.jdi.request.ThreadDeathRequest createThreadDeathRequest ()
15 com.sun.jdi.request.ThreadStartRequest createThreadStartRequest ()
16 com.sun.jdi.request.VMDeathRequest createVMDeathRequest ()

8. Smyčka událostí implementovaná s využitím objektu typu com.sun.jdi.event.EventQueue

Vlastní implementace smyčky událostí je rozdělená do dvou uživatelských metod. V metodě nazvané příznačně eventLoop() se nejprve získá objekt typu EventQueue a následně se v programové smyčce opakovaně volá uživatelská metoda processEvents() vracející 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 ukončit svoji činnost:

    /**
     * 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(vmDeathRequest, eventQueue)) {
            // jojo tady skutecne nic neni :)
        }
    }

9. Zpracování jednotlivých typů událostí a reakce na ukončení práce sledované JVM

Konečně se dostáváme k nejdůležitější části celého debuggeru – k vlastnímu čtení a zpracování jednotlivých událostí. Důležité je si uvědomit, že události se z fronty událostí čtou po skupinách uložených v objektu typu EventSet (což jsme si již ostatně řekli ve druhé kapitole). Vzhledem k tomu, že EventSet implementuje rozhraní CollectionIterable, je snadné iterovat přes všechny události uložené v tomto objektu. Ve vnitřní smyčce je proveden rozeskok v závislosti na typu události, přičemž předpokládáme, že se budou číst pouze události typu VMStartEvent a VMDeathEvent (teoreticky též VMDisconnectEvent, ovšem my ukončíme debugger dříve, než tato událost nastane :-). Jakmile získáme první událost typu VMDeathEvent, dojde k ukončení debuggeru – ze smyčky se vrátí hodnota false, která ve funkci eventLoop (předchozí kapitola) povede k ukončení:

    /**
     * Precteni a zpracovani udalosti
     *
     * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent
     * @param eventQueue fronta udalosti
     *
     * @return true pokud se ma pokracovat ve zpracovavani udalosti
     *         false pokud se ma sledovana VM ukoncit
     */
    private static boolean processEvents(VMDeathRequest vmDeathRequest, EventQueue eventQueue) {
        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 {
                    System.out.println("other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

Volání metody EventSet.resume() slouží ke znovuspuštění vláken, které jsou při vzniku události ve sledované JVM pozastaveny. V našem případě sice není volání této metody nutné, protože zpracováváme pouze události platné pro celou JVM, ale pro jistotu zde tato metoda je uvedena (příště bude navíc přemístěna do bloku finally).

10. Kompletní zdrojový kód demonstračního příkladu JDIEventRequestManager

V předchozích kapitolách jsme si popsali důležité metody, které jsou využity v demonstračním příkladu JDIEventRequestManager. Kompletní zdrojový kód tohoto příkladu je vypsán pod tímto odstavcem, odkaz na poslední verzi zdrojového kódu uloženého do Mercurial repositáře naleznete v jedenácté kapitole:

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.Bootstrap;
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.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.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
 *
 * Po pripojeni se zaregistruje udalost zavolana ve chvili, kdy dojde k ukonceni
 * behu sledovaneho virtualniho stroje Javy.
 *
 * @author Pavel Tisnovsky
 */
public class JDIEventRequestManager {
 
    /**
     * 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";
 
    /**
     * 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();
        VMDeathRequest vmDeathRequest = registerVMDeathEvent(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;
    }
 
    /**
     * 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(vmDeathRequest, eventQueue)) {
            // jojo tady skutecne nic neni :)
        }
    }
 
    /**
     * Precteni a zpracovani udalosti
     *
     * @param vmDeathRequest objekt pro rizeni udalosti typu VMDeathEvent
     * @param eventQueue fronta udalosti
     *
     * @return true pokud se ma pokracovat ve zpracovavani udalosti
     *         false pokud se ma sledovana VM ukoncit
     */
    private static boolean processEvents(VMDeathRequest vmDeathRequest, EventQueue eventQueue) {
        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 {
                    System.out.println("other event");
                }
            }
            // znovu postit vsechna vlakna
            eventSet.resume();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
 
}

Pokud je tento jednoduchý debugger připojen k virtuálnímu stroji Javy s testovací aplikací Test4, měl by na standardní výstup vypsat následující text:

Connecting to virtual machine
Connected
Got 1 request:
    VMStartEvent
Got 2 requests:
    VMDeathEvent
Calling exit

Po výpisu těchto zpráv by debugger měl ukončit jak cílovou JVM, tak i sám sebe. Poznámka: testovací aplikace Test4 čeká na stisk libovolné klávesy, teprve poté dojde ke vzniku události typu VMDeathEvent:

import java.io.IOException;
 
public class Test4 {
 
    public static void main(String[] args) throws IOException {
        System.out.println("Press any key");
        System.in.read();
    }
 
}

11. Repositář se zdrojovými kódy demonstračního příkladu JDIEventRequestManager i podpůrných skriptů

Zdrojové kódy dnešního demonstračního příkladu JDIEventRequestManager, testovací třídy Test4 i skriptů použitých pro překlad i spuštění tohoto demonstračního příkladu, byly uloženy (podobně jako tomu bylo i v mnoha předchozí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 všech zmíněných zdrojových souborů a skriptů můžete najít na adresách:

12. 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?

17. 5. 2013 14:07

Nj. to je problem trosku odbornejsich clanku. Kdybych napsal clanek na tema Firefox vs. IE, Wokna vs. Linux nebo KDE vs. Gnome hell, asi by bylo vic komentaru :-)

16. 5. 2013 23:57

S JDI sice bezne neprogramuji ale na rozsireni obzoru je to opravdu pekne cteni. Trosku me zarazi ze tu nejsou zadne komentare, bereme-li v potaz kolik lidi Java zivi. Mozna jim staci ze 'v tom IDEcku' nejak ten debugger funguje...

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Vitalia.cz: Taky věříte na pravidlo 5 sekund?

Taky věříte na pravidlo 5 sekund?

Lupa.cz: Brněnský radní chce zničit kartel operátorů. Uspěje?

Brněnský radní chce zničit kartel operátorů. Uspěje?

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: Udávání a účtenková loterie, hloupá komedie

Udávání a účtenková loterie, hloupá komedie

Vitalia.cz: Jedlé kaštany jsou trpké, je třeba je tepelně upravit

Jedlé kaštany jsou trpké, je třeba je tepelně upravit

Lupa.cz: Co se dá měřit přes Internet věcí

Co se dá měřit přes Internet věcí

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?