Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI (2)

16. 4. 2013
Doba čtení: 19 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si řekneme, jakým způsobem lze využít metody nabízené rozhraním com.sun.jdi.VirtualMachine pro získání základních informací o cílovém virtuálním stroji Javy, zejména o vláknech a třídách spravovaných tímto JVM.

Obsah

1. Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI (2)

2. Připojení debuggeru k cílové JVM

3. Získání konektoru, který pro připojení k JVM využívá sockety

4. Připojení debuggeru k běžícímu virtuálnímu stroji přes socket

5. Nastavení parametrů JDI konektoru

6. Metody connectToVirtualMachine a shutdownVirtualMachine

7. Výpis základních informací o JVM, k níž se debugger připojil

8. Výpis informací o vláknech existujících ve cílové JVM

9. Základní informace o vláknech, které lze přes JDI získat

10. Výpis informací o třídách načtených do cílové JVM

11. Kompletní zdrojový kód demonstračního příkladu JDIVirtualMachineInfo

12. Překlad a spuštění demonstračního příkladu

13. Zdrojové kódy demonstračního příkladu i podpůrných skriptů

14. Odkazy na Internetu

1. Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI (2)

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již počtvrté vrátíme k popisu rozhraní JDI (Java Debugger Interface). Již v předchozích dílech jsme si poměrně dopodrobna popsali dva základní způsoby připojení debuggeru či jiného podobného nástroje k takzvanému sledovanému (cílovému) virtuálnímu stroji Javy. Nyní se již k tomuto tématu nebudeme vracet a budeme předpokládat, že je cílová JVM vždy spuštěna takovým způsobem, aby se k ní mohl debugger připojit přes sockety. Konkrétně to znamená, že cílová JVM bude spuštěna následujícím způsobem (důležité je zde především zvolené číslo portu, které musí znát i debugger):

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

Dnes si řekneme, jak lze s využitím metod nabízených rozhraním com.sun.jdi.VirtualMachine získat základní informace o cílové JVM. Zajímat nás budou zpočátku informace o vláknech vytvořených v cílové JVM (ať již se jedná o vlákna běžící či naopak o vlákna pozastavená) i informace o třídách, které byly do tohoto virtuálního stroje Javy načteny. Podobně, jako tomu bylo v předchozí části tohoto seriálu, se ukáže, že javovské rozhraní JDI je na použití mnohem jednodušší, než nativní rozhraní JVM TI.

2. Připojení debuggeru k cílové JVM

Podobně jako minule, i dnes si funkce nabízené rozhraním JDI popíšeme a využijeme v relativně jednoduchém demonstračním příkladu, jehož úplný zdrojový kód najdete v jedenácté kapitole. Tento demonstrační příklad představuje kostru debuggeru, který se k cílovému virtuálnímu stroji Javy připojuje přes sockety. To znamená, že je nejprve nutné získat všechny nabízené konektory implementující rozhraní com.sun.jdi.connect.Attachin­gConnector s využitím metody VirtualMachineManager.Attachin­gConnector() a posléze je nutné z tohoto seznamu vybrat jediný konektor umožňující spojení s cílovou JVM přes sockety – samotný výběr konektoru zajišťuje uživatelská metoda getSocketAttachConnector() vypsaná v následující kapitole. Jakmile se příslušný konektor získá, zavolá se další uživatelská metoda nazvaná debugVirtualMachineUsingSocket(), jejímž popisem se budeme zabývat v kapitole čtvrté:

    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;
        }
 
        debugVirtualMachineUsingSocket(connector);
    }

3. Získání konektoru, který pro připojení k JVM využívá sockety

Metoda getSocketAttachConnector(), která je volána přímo z metody main() [kapitola 2], je implementována sice jednoduše, ovšem (alespoň zdánlivě) neefektivně. Porovnáváme zde totiž jména jednotlivých konektorů s řetězcovou konstantou (literálem) a pokud je nalezen konektor se jménem „com.sun.jdi.SocketAttach“ (tento řetězec je uložen v konstantě SOCKET_ATTACH_CONNECTOR_NAME), je tento konektor metodou ihned vrácen. Na tomto místě je vhodné poznamenat, že vzhledem k současnému stavu JDI zde není možné použít například operátor instanceof, protože konkrétní jméno třídy či rozhraní, kterou musí konektor typu „SocketAttach“ implementovat, není přesně zdokumentováno a stabilizováno mezi různými implementacemi JVM (poznámka: zde popisovaná metoda je shodná se stejně pojmenovanou metodou použitou v předchozí části tohoto seriálu):

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

4. Připojení debuggeru k běžícímu virtuálnímu stroji přes socket

Dostáváme se k metodě debugVirtualMachineUsingSocket(), která tvoří ústřední část našeho zatím stále ještě velmi jednoduchého debuggeru. V této metodě se nejprve přes uživatelskou metodu prepareConnectorArguments() [kapitola 5] nastaví všechny argumenty používané konektorem při připojování k cílovému virtuálnímu stroji Javy, provede se vlastní připojení v další uživatelské metodě connectToVirtualMachine() [kapitola 6] a posléze se na standardní výstup vytisknou všechny informace získané z cílové JVM. Následně je cílová JVM zastavena v metodě shutdownVirtualMachine() [kapitola 6], a to především z toho důvodu, že by bylo poměrně obtížné navázat s cílovou JVM další spojení ve chvíli, kdy se již jeden debugger odpojil.

    /**
     * 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);
 
            // vypis zakladnich informaci o pripojenem VM
            printVirtualMachineInfo(virtualMachine);
 
            // ukonceni behu vzdaleneho virtualniho stroje
            shutdownVirtualMachine(virtualMachine);
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
    }

5. Nastavení parametrů JDI konektoru

Ve chvíli, kdy již máme vybrán správný konektor, je možné se připojit k běžícímu virtuálnímu stroji Javy. Aby se připojení skutečně provedlo, musí se nejprve nastavit port, přes který se debugger k cílové JVM připojí. My jsme si zvolili port číslo 6502 a nastavení příslušného parametru (či přesněji řečeno argumentu) konektoru je provedeno v samostatné uživatelské metodě nazvané prepareConnectorArguments(). Tato metoda pracuje velmi jednoduše – nejprve přečte všechny výchozí argumenty konektoru a posléze změní argument uložený pod klíčem „port“. Připomeňme si, že mapa vrácená přes AttachingConnector.defaultArguments() je neměnitelná v tom smyslu, že do ní nelze přidávat či z ní odebírat další prvky, ovšem hodnoty jednotlivých prvků je samozřejmě možné měnit:

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

6. Metody connectToVirtualMachine a shutdownVirtualMachine

V této kapitole si stručně popíšeme význam dvou pomocných uživatelských metod nazvaných connectToVirtualMachine a shutdownVirtualMachine. První z těchto metod slouží k připojení k cílovému virtuálnímu stroji Javy, což je uvnitř metody zajištěno voláním AttachingConnector.attach(), kterému se předá mapa obsahující nové argumenty (parametry) konektoru. V našem demonstračním debuggeru je modifikován argument „port“ v již popsané metodě prepareConnectorArguments() [kapitola 5]:

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

Další pomocná metoda nazvaná shutdownVirtualMachine() slouží pro násilné ukončení cílové JVM s předáním návratového kódu, který je následně vrácen operačnímu systému:

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

7. Výpis základních informací o JVM, k níž se debugger připojil

Jakmile se náš demonstrační debugger připojí k cílovému virtuálnímu stroji Javy, je možné s využitím rozhraní com.sun.jdi.VirtualMachine získat mnoho důležitých informací o sledované JVM. Všechny informace jsou získávány a vypisovány v uživatelské metodě nazvané printVirtualMachineInfo(), z níž se volá čtveřice dalších uživatelských metod printDescription(), printVersion(), printThreadInfo() a printClassesInfo():

    /**
     * Vypis informaci ziskanych ze sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printVirtualMachineInfo(VirtualMachine virtualMachine) {
        System.out.println("Basic virtual machine info:");
        printDescription(virtualMachine);
        printVersion(virtualMachine);
        printThreadInfo(virtualMachine);
        printClassesInfo(virtualMachine);
    }

Zajímavá je metoda printDescription(), která na standardní výstup vypíše podrobnější víceřádkový popis propojení mezi cílovým JVM a debuggerem. Pro tento účel se uvnitř této metody volá bezparametrická metoda VirtualMachine.description() vracející řetězec:

    /**
     * Slovni viceradkovy popis sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printDescription(VirtualMachine virtualMachine) {
        String description = virtualMachine.description();
        System.out.println("Description: " + description);
        System.out.println();
    }

Zpráva získaná touto metodou může vypadat například takto:

Java Debug Interface (Reference Implementation) version 1.6
Java Debug Wire Protocol (Reference Implementation) version 1.6
JVM Debug Interface version 1.1
JVM version 1.6.0_01 (Java HotSpot(TM) Client VM, mixed mode, sharing)

Další metoda vypíše verzi cílového virtuálního stroje Javy, kterou je možné použít například pro rozhodování, které informace je možné z této JVM získat. Uvnitř je využito volání bezparametrické metody VirtualMachine.version() vracející řetězec (nikoli celé číslo!):

    /**
     * Verze sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printVersion(VirtualMachine virtualMachine) {
        String version = virtualMachine.version();
        System.out.println("Version: " + version);
        System.out.println();
    }

Zpráva vypsaná touto metodou může vypadat takto:

Version: 1.6.0_01

8. Výpis informací o vláknech existujících ve cílové JVM

S využitím metod dostupných přes rozhraní com.sun.jdi.VirtualMachine je možné přečíst i informace o všech vláknech vytvořených ve sledovaném virtuálním stroji Javy. Jedná se o vlákna, která se mohou nacházet v libovolném stavu, tj. o vlákna běžící, pozastavená, ještě nespuštěná, vlákna čekající na uvolnění zámku atd. atd. Seznam všech vláken se přečte metodou VirtualMachine.allThreads(), přičemž prvky tohoto seznamu jsou typu com.sun.jdi.ThreadReference. Každý objekt tohoto typu obsahuje mnoho informací o jednotlivých vláknech, například unikátní identifikátor vlákna, jeho jméno, stav vlákna a taktéž informaci o tom, zda je vlákno pozastaveno samotným debuggerem. O přečtení informací o všech vláknech a následný výpis těchto informací se stará uživatelská metoda nazvaná printThreadInfo():

    /**
     * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printThreadInfo(VirtualMachine virtualMachine) {
        System.out.println("Thread info:");
        System.out.println("    UniqueID    Thread name            Status       Suspended");
        List<ThreadReference> threads = virtualMachine.allThreads();
        for (ThreadReference thread : threads) {
            String threadName = thread.name();
            String threadStatus = getThreadStatus(thread);
            String threadSuspended = getThreadSuspended(thread);
            long uniqueID = thread.uniqueID();
            System.out.format("    %8d    %-20s   %-12s    %-5s\n", uniqueID, threadName, threadStatus, threadSuspended);
        }
        System.out.println();
    }

Použité metody:

String  com.sun.jdi.ThreadReference.name()
long    com.sun.jdi.ObjectReference.uniqueID() - zděděno
int     com.sun.jdi.ThreadReference.status()
boolean com.sun.jdi.ThreadReference.isSuspended()

9. Základní informace o vláknech, které lze přes JDI získat

Uživatelská metoda printThreadInfo() popsaná v předchozí kapitole ve skutečnosti volá dvě další uživatelské metody nazvané getThreadStatus() a getThreadSuspended(). Metoda getThreadStatus() vrací stav vlákna jako řetězec. Ve skutečnosti je totiž stav vlákna reprezentován celočíselnou konstantou (nikoli plnohodnotným výčtovým typem), který lze na řetězec převést například s využitím strukturovaného příkazu switch (osobně si myslím, že by tento příkaz nemusel být v objektově orientovaném jazyce vůbec implementován, ovšem jeho interní implementace je v případě celočíselných konstant poměrně efektivní, a to jak po paměťové stránce, tak po stránce efektivity vygenerovaného bajtkódu):

    /**
     * Prevod stavu vlakna na retezec.
     *
     * @param thread JDI objekt predstavujici vlakno
     * @return stav vlakna v retezcove podobe
     */
    private static String getThreadStatus(ThreadReference thread) {
        switch (thread.status()) {
        case ThreadReference.THREAD_STATUS_NOT_STARTED:
            return "not started";
        case ThreadReference.THREAD_STATUS_RUNNING:
            return "running";
        case ThreadReference.THREAD_STATUS_SLEEPING:
            return "sleeping";
        case ThreadReference.THREAD_STATUS_MONITOR:
            return "wait/monitor";
        case ThreadReference.THREAD_STATUS_WAIT:
            return "Object.wait";
        case ThreadReference.THREAD_STATUS_ZOMBIE:
            return "zombie";
        case ThreadReference.THREAD_STATUS_UNKNOWN:
            return "*unkwnown*";
        default:
            return "should not happen!";
        }
    }

Další pomocná uživatelská metoda nazvaná getThreadSuspended() je velmi jednoduchá – vrací pouze řetězec „yes“ či „no“ na základě toho, zda je dané vlákno v cílovém virtuálním stroji Javy pozastaveno debuggerem či nikoli (tento stav je nutné odlišit od původního stavu vlákna):

    /**
     * Informace (ve tvaru retezce) o tom, zda je vlakno pozastaveno ci nikoli.
     *
     * @param thread JDI objekt predstavujici vlakno
     * @return stav pozastaveni vlakna v retezcove podobe
     */
    private static String getThreadSuspended(ThreadReference thread) {
        return thread.isSuspended() ? "yes" : "no";
    }

10. Výpis informací o třídách načtených do cílové JVM

Mezi další důležité informace o cílovém virtuálním stroji Javy patří seznam tříd, které jsou do této JVM načteny jak při inicializaci virtuálního stroje, tak i při běhu aplikace. Všechny dostupné třídy lze získat s využitím metody com.sun.jdi.VirtualMachine­.allClasses() vracející seznam objektů typu com.sun.jdi.ReferenceType, což je třída odvozená od obecnější třídy com.sun.jdi.Type, s níž se pravděpodobně seznámíme příště. O každé třídě lze získat mnoho informací, samozřejmě včetně jejího jména a signatury. Aby nebyl výpis příliš dlouhý, omezíme se v našem testovacím příkladu na výpis tříd obsahujících řetězec „String“:

    /**
     * Vypis zakladnich informaci o tridach nactenych do sledovane JVM.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printClassesInfo(VirtualMachine virtualMachine) {
        System.out.println("Class info:");
        System.out.format("    %-45s    %-45s   %s\n", "Name", "Signature", "Source");
        List<ReferenceType> classes = virtualMachine.allClasses();
        for (ReferenceType klass : classes) {
            String className = klass.name();
            /* jednoduchy filtr */
            if (!className.contains("String")) continue;
            String classSignature = klass.signature();
            String sourceName;
            try {
                sourceName = klass.sourceName();
            }
            catch (AbsentInformationException e) {
                sourceName = "unknown";
            }
            System.out.format("    %-45s    %-45s   %s\n", className, classSignature, sourceName);
        }
        System.out.println();
    }

Použité metody:

String com.sun.jdi.ReferenceType.name()
String com.sun.jdi.ReferenceType.sourceName()
String com.sun.jdi.Type.signature() - zděděno

11. Kompletní zdrojový kód demonstračního příkladu JDIVirtualMachineInfo

V předchozích kapitolách byly popsány prakticky všechny metody, které jsou součástí dnešního demonstračního příkladu tvořícího kostru debuggeru. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto velmi jednoduchého debuggeru:

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.IncompatibleThreadStateException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
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;
 
/**
 * 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 JDIVirtualMachineInfo {
 
    /**
     * 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";
 
    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;
        }
 
        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;
            }
        }
        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);
 
            // vypis zakladnich informaci o pripojenem VM
            printVirtualMachineInfo(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;
    }
 
    /**
     * 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);
    }
 
    /**
     * Vypis informaci ziskanych ze sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printVirtualMachineInfo(VirtualMachine virtualMachine) {
        System.out.println("Basic virtual machine info:");
        printDescription(virtualMachine);
        printVersion(virtualMachine);
        printThreadInfo(virtualMachine);
        printClassesInfo(virtualMachine);
    }
 
    /**
     * Slovni viceradkovy popis sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printDescription(VirtualMachine virtualMachine) {
        String description = virtualMachine.description();
        System.out.println("Description: " + description);
        System.out.println();
    }
 
    /**
     * Verze sledovaneho virtualniho stroje.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printVersion(VirtualMachine virtualMachine) {
        String version = virtualMachine.version();
        System.out.println("Version: " + version);
        System.out.println();
    }
 
    /**
     * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printThreadInfo(VirtualMachine virtualMachine) {
        System.out.println("Thread info:");
        System.out.println("    UniqueID    Thread name            Status       Suspended");
        List<ThreadReference> threads = virtualMachine.allThreads();
        for (ThreadReference thread : threads) {
            String threadName = thread.name();
            String threadStatus = getThreadStatus(thread);
            String threadSuspended = getThreadSuspended(thread);
            long uniqueID = thread.uniqueID();
            System.out.format("    %8d    %-20s   %-12s    %-5s\n", uniqueID, threadName, threadStatus, threadSuspended);
        }
        System.out.println();
    }
 
    /**
     * Prevod stavu vlakna na retezec.
     *
     * @param thread JDI objekt predstavujici vlakno
     * @return stav vlakna v retezcove podobe
     */
    private static String getThreadStatus(ThreadReference thread) {
        switch (thread.status()) {
        case ThreadReference.THREAD_STATUS_NOT_STARTED:
            return "not started";
        case ThreadReference.THREAD_STATUS_RUNNING:
            return "running";
        case ThreadReference.THREAD_STATUS_SLEEPING:
            return "sleeping";
        case ThreadReference.THREAD_STATUS_MONITOR:
            return "wait/monitor";
        case ThreadReference.THREAD_STATUS_WAIT:
            return "Object.wait";
        case ThreadReference.THREAD_STATUS_ZOMBIE:
            return "zombie";
        case ThreadReference.THREAD_STATUS_UNKNOWN:
            return "*unkwnown*";
        default:
            return "should not happen!";
        }
    }
 
    /**
     * Informace (ve tvaru retezce) o tom, zda je vlakno pozastaveno ci nikoli.
     *
     * @param thread JDI objekt predstavujici vlakno
     * @return stav pozastaveni vlakna v retezcove podobe
     */
    private static String getThreadSuspended(ThreadReference thread) {
        return thread.isSuspended() ? "yes" : "no";
    }
 
    /**
     * Vypis zakladnich informaci o tridach nactenych do sledovane JVM.
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printClassesInfo(VirtualMachine virtualMachine) {
        System.out.println("Class info:");
        System.out.format("    %-45s    %-45s   %s\n", "Name", "Signature", "Source");
        List<ReferenceType> classes = virtualMachine.allClasses();
        for (ReferenceType klass : classes) {
            String className = klass.name();
            /* jednoduchy filtr */
            if (!className.contains("String")) continue;
            String classSignature = klass.signature();
            String sourceName;
            try {
                sourceName = klass.sourceName();
            }
            catch (AbsentInformationException e) {
                sourceName = "unknown";
            }
            System.out.format("    %-45s    %-45s   %s\n", className, classSignature, sourceName);
        }
        System.out.println();
    }
}

12. Překlad a spuštění demonstračního příkladu

Překlad demonstračního příkladu zajistí následující příkaz, který byl odladěn na OpenJDK6. Pokud je využita jiná JDK či odlišná verze OpenJDK, je nutné upravit cestu k java archivu tools.jar:

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

Pro kontrolu funkčnosti našeho demonstračního příkladu se použije následující testovací třída (stejná třída byla použita i minule):

public class Test {
 
    public static void main(String[] args) {
        while (true) {
            System.out.println("Hello world!");
        }
    }
 
}

Po překladu testovací třídy:

javac Test

se provede její spuštění následujícím způsobem:

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

Demonstrační debugger, který byl popsaný v předchozích kapitolách, je nutné spustit tak, aby virtuální stroj Javy našel i archiv tools.jar:

bitcoin_skoleni

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

Po spuštění by se měla na standardním výstupu objevit přibližně tato zpráva (její konkrétní znění se může lišit v závislosti na tom, jaká JRE je použita pro spuštění cílové JVM):

Connecting to virtual machine
Connected
Basic virtual machine info:
Description: Java Debug Interface (Reference Implementation) version 1.6
Java Debug Wire Protocol (Reference Implementation) version 1.6
JVM Debug Interface version 1.1
JVM version 1.6.0_01 (Java HotSpot(TM) Client VM, mixed mode, sharing)
 
Version: 1.6.0_01
 
Thread info:
    UniqueID    Thread name            Status       Suspended
           2    Attach Listener        running         yes
           3    Signal Dispatcher      running         yes
           4    Finalizer              Object.wait     yes
           5    Reference Handler      Object.wait     yes
           1    main                   running         yes
 
Class info:
    Name                                             Signature                                       Source
    java.lang.AbstractStringBuilder                  Ljava/lang/AbstractStringBuilder;               AbstractStringBuilder.java
    java.lang.String                                 Ljava/lang/String;                              String.java
    java.lang.String$CaseInsensitiveComparator       Ljava/lang/String$CaseInsensitiveComparator;    String.java
    java.lang.StringBuffer                           Ljava/lang/StringBuffer;                        StringBuffer.java
    java.lang.StringBuilder                          Ljava/lang/StringBuilder;                       StringBuilder.java
    java.lang.StringCoding                           Ljava/lang/StringCoding;                        StringCoding.java
    java.lang.StringCoding$StringDecoder             Ljava/lang/StringCoding$StringDecoder;          StringCoding.java
    java.lang.StringCoding$StringEncoder             Ljava/lang/StringCoding$StringEncoder;          StringCoding.java
    java.lang.String[]                               [Ljava/lang/String;                             unknown
    java.util.StringTokenizer                        Ljava/util/StringTokenizer;                     StringTokenizer.java
 
Calling exit

13. Zdrojové kódy demonstračního příkladu i podpůrných skriptů

Zdrojové kódy demonstračního příkladu, testovací třídy i skriptů použitých pro překlad a spuštění demonstračního příkladu, byly uloženy 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:

14. Odkazy na Internetu

  1. Breakpoint (Wikipedia)
    http://cs.wikipedia.org/wi­ki/Breakpoint
  2. JVM Tool Interface Version 1.2 Documentation
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  3. JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#SetBreakpoint
  4. JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#ClearBreakpoint
  5. JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#Breakpoint
  6. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  7. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  8. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  9. 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
  10. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  11. 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
  12. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  13. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  14. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  15. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  16. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  17. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  18. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  19. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.