Pohled pod kapotu JVM – přístup k zásobníkovým rámcům vláken sledované JVM přes rozhraní JDI

23. 4. 2013
Doba čtení: 18 minut

Sdílet

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si ukážeme, jakým způsobem je možné s využitím rozhraní JDI (Java Debugger Interface) získat a následně i vypsat obsah zásobníkových rámců všech vláken aplikace běžící v cílové (sledované) JVM.

Obsah

1. Pohled pod kapotu JVM – přístup k zásobníkovým rámcům vláken sledované JVM přes rozhraní JDI

2. Získání a následný výpis obsahu zásobníkových rámců s využitím rozhraní JDI

3. Výpis informací o vybraném zásobníkovém rámci

4. Přečtení jména volané metody i jména třídy, v níž je tato metoda deklarována

5. Získání řádku, v níž došlo k volání metody a přečtení jména zdrojového souboru třídy

6. Kompletní zdrojový kód demonstračního příkladu JDIStackTraceList

7. Spuštění demonstračního příkladu

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

9. Odkazy na Internetu

1. Pohled pod kapotu JVM – přístup k zásobníkovým rámcům vláken sledované JVM přes rozhraní JDI

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již popáté budeme zabývat možnostmi nabízenými rozhraním JDI (Java Debugger Interface). Ukážeme si, jakým způsobem je možné toto rozhraní použít k získání obsahu zásobníkových rámců všech vláken aplikace běžící v cílové (sledované) JVM. Dnešní demonstrační příklad JDIStackTraceList sloužící k výpisu obsahu zásobníkových rámců jednotlivých vláken je založen na příkladu JDIVirtualMachineInfo, jehož zdrojový kód jsme si do všech podrobností popsali minule. Před popisem dnešního demonstračního příkladu i dalších možností rozhraní JDI si však nejprve připomeňme, jakým způsobem interně pracuje virtuální stroj Javy při spuštění a následném běhu javovské aplikace.

Při spuštění JVM i v průběhu inicializace javovské aplikace je pro každé vlákno vytvořen samostatný zásobník (stack) a pokud se v nějakém vláknu volá metoda, je pro toto volání na zásobníku vytvořen takzvaný zásobníkový rámec (stack frame), v němž je uložena jak informace o bodu návratu z metody (tedy místo, kam povede instrukce typu return v bajtkódu), tak i hodnoty všech parametrů předaných metodě i oblast s lokálními proměnnými metody. Označení „zásobník“ sice může navozovat pocit, že se jedná o kontinuální oblast paměti, ve skutečnosti však mezi jednotlivými zásobníkovými rámci neexistují žádné přímé vazby a proto se každý zásobníkový rámec může (ale nemusí) nacházet v paměti kdekoli – rámce například mohou být vytvářeny přímo na haldě (hodně zde záleží na konkrétní implementaci JVM). Podle specifikace JVM je totiž každý zásobník (stack) tvořen sekvencí vzájemně pouze nepřímo propojených zásobníkových rámců (stack frame(s)), což má i své dopady na větší bezpečnost javovských aplikací.

2. Získání a následný výpis obsahu zásobníkových rámců s využitím rozhraní JDI

Při použití rozhraní JVM TI bylo možné k zásobníkovým rámcům přistupovat například v callback funkcích zavolaných ve chvíli vzniku výjimky, popř. v callback funkcích zavolaných ve chvíli, kdy došlo ve sledované javovské aplikaci k zavolání nějaké metody. Tento přístup měl několik předností, ale i záporů. Pravděpodobně největší předností byl fakt, že při automatickém zavolání callback funkce zaregistrované v JVM TI agentovi došlo k pozastavení daného vlákna, což znamenalo, že bylo možné relativně bez problémů zjistit všechny zásobníkové rámce tohoto vlákna a provádět s nimi různé operace aniž by to ovlivnilo další vlákna aplikace. Nevýhodou bylo to, že přístup k zásobníkovým rámcům všech vláken již byl o poznání složitější (nehledě na složitost celého nízkoúrovňového JVM TI). Při použití rozhraní JDI se k problému přistupuje vlastně z opačné strany – máme totiž přístup ke všem vláknům aplikace a můžeme zjistit a zpracovat zásobníkové rámce libovolného vlákna, ovšem pouze v případě, že je toto vlákno pozastaveno (což je většinou nutné zařídit programově přímo z JDI).

Připomeňme si, že sledovaný (cílový) virtuální stroj Javy se spouští s následujícími parametry:

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

Tyto parametry mj. zajistí i to, že ihned po spuštění virtuálního stroje a po inicializaci aplikace dojde k pozastavení všech vláken uvnitř JVM. Znamená to taktéž, že vlastně ani nedojde ke spuštění metody main v aplikaci, která má být v JVM spuštěna, takže výsledky, které z takto pozastaveného virtuálního stroje Javy získáme, budou poměrně nezajímavé. Ovšem díky rozhraní JDI není velkým problémem cílovou JVM na chvíli spustit, čímž již dojde ke spuštění metody main. Po přibližně jedné sekundě dojde k opětovnému pozastavení JVM. V našem demonstračním příkladu je spuštění a opětovné pozastavení cílové JVM zajištěno v uživatelské metodě nazvané runVirtualMachineForOneSecond, která vypadá následovně:

    /**
     * Spustit sledovany virtualni stroj po dobu jedne sekundy
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachineForOneSecond(VirtualMachine virtualMachine) {
        virtualMachine.resume();
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        virtualMachine.suspend();
    }

Jakmile je cílová JVM opět pozastavena, je možné získat informace o všech vláknech a taktéž přečíst informace o všech zásobníkových rámcích jednotlivých vláken. V demonstračním příkladu je tato funkcionalita implementována v metodě printThreadInfo, kterou již částečně známe z předchozí části tohoto seriálu (zde pouze přibyl řádek s voláním metody printStackFrames):

    /**
     * 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);
            printStackFrames(thread);
        }
        System.out.println();
    }

Použité metody rozhraní JDI:

# Třída/rozhraní Návratový typ Metoda
1 com.sun.jdi.VirtualMachine void resume()
2 com.sun.jdi.VirtualMachine void suspend()
3 com.sun.jdi.ThreadReference String name()
4 com.sun.jdi.ObjectReference long uniqueID()
5 com.sun.jdi.ThreadReference int status()
6 com.sun.jdi.ThreadReference boolean isSuspended()

3. Výpis informací o vybraném zásobníkovém rámci

Uživatelské metodě nazvané printStackFrames se předává objekt typu ThreadReference, který představuje vlákno v cílové JVM. Nejprve je voláním com.sun.jdi.ThreadReferen­ce.frameCount() získán počet zásobníkových rámců vytvořených pro vybrané vlákno a následně se projde všemi zásobníkovými rámci tohoto vlákna. Pro získání všech zásobníkových rámců se využívá metoda com.sun.jdi.ThreadReference.frames() vracející seznam objektů typu com.sun.jdi.StackFrame:

    /**
     * Vypis zasobnikovych ramcu
     *
     * @param thread JDI objekt predstavujici vlakno
     */
    private static void printStackFrames(ThreadReference thread) {
        try {
            System.out.format("%16s-------------------------------------------------\n", "");
            System.out.format("%16sStack frame count: %d\n", "", thread.frameCount());
            // vypsat informace o vsech zasobnikovych ramcich
            for (StackFrame frame : thread.frames()) {
                printStackFrameInfo(frame);
            }
            System.out.format("%16s-------------------------------------------------\n", "");
        }
        catch (IncompatibleThreadStateException e) {
            e.printStackTrace();
        }
    }

V uživatelské metodě printStackFrameInfo se již zjišťují a následně i vypisují informace o jednotlivých zásobníkových rámcích (o významu třídy Location si řekneme více informací v dalším textu):

    /**
     * Vypis informaci o vybranem zasobnikovem ramci
     *
     * @param frame zasobnikovy ramec
     */
    private static void printStackFrameInfo(StackFrame frame) {
        Location location = frame.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 mame vsechny informace, lze je tedy vypsat
        System.out.format("%16s%s.%s (%s:%s)\n", "", className, methodName, sourceName, lineNumber);
    }

Význam metod getClassName, getMethodName, getSourceName a getLineNumber bude podrobněji vysvětlen ve čtvrté a v páté kapitole.

Použité metody rozhraní JDI:

# Třída/rozhraní Návratový typ Metoda
1 com.sun.jdi.ThreadReference int frameCount()
2 com.sun.jdi.ThreadReference List<StackFrame> frames()
3 com.sun.jdi.StackFrame com.sun.jdi.Location location()

4. Přečtení jména volané metody i jména třídy, v níž je tato metoda deklarována

Každý vývojář používající programovací jazyk Java se již s velkou pravděpodobností setkal s takzvaným stack trace, což vlastně není nic jiného, než vhodným způsobem naformátovaný seznam volaných metod. V tomto seznamu se mj. vypisuje i jméno metody a taktéž jméno třídy, v níž byla tato metoda deklarována. Tyto dvě informace je možné s využitím rozhraní JDI získat velmi snadno. Nejdříve je nutné z objektu typu com.sun.jdi.StackFrame získat objekt typu com.sun.jdi.Location obsahující veškeré dostupné informace o volané metodě. Jakmile máme tento objekt k dispozici, lze snadno získat referenci volané metody a následně i jméno této metody, což je v demonstračním příkladu implementováno v getMethodName:

    /**
     * Jmeno volane metody.
     */
    private static String getMethodName(Location location) {
        return location.method().name();
    }

Můžeme taktéž získat třídu, ve které je volaná metoda deklarována. Používá se zde volání com.sun.jdi.Method.declaringType() (přesněji řečeno com.sun.jdi.TypeComponent­.declarintType()) vracející objekt typu ReferenceType:

    /**
     * Jmeno tridy, jejiz metoda byla zavolana.
     */
    private static String getClassName(Location location) {
        return location.method().declaringType().name();
    }

Použité metody rozhraní JDI:

# Třída/rozhraní Návratový typ Metoda
1 com.sun.jdi.Location com.sun.jdi.Method method()
2 com.sun.jdi.Method String name()
3 com.sun.jdi.Method com.sun.jdi.ReferenceType declaringType()
4 com.sun.jdi.ReferenceType String name()

5. Získání řádku, v níž došlo k volání metody a přečtení jména zdrojového souboru třídy

V každém stack trace se kromě jména třídy a jména volané metody objevují i další dvě informace. Jedná se o číslo řádku s voláním metody a taktéž o jméno zdrojového souboru obsahujícího deklaraci příslušné třídy či rozhraní – tato informace ovšem nemusí být dostupná vždy. Nejprve se podívejme, jak lze zjistit číslo řádku, v němž došlo k volání metody. Tuto informaci získáme pomocí com.sun.jdi.Location.lineNumber(), která vrátí kladné číslo v případě, že je číslo řádku známé a zápornou či nulovou hodnotu ve chvíli, kdy číslo řádku není možné z nějakého důvodu zjistit (typicky se jedná o nativní metody). Na tomto místě je ještě vhodné upozornit na to, že rozhraní JDI operuje s pojmem „strata“ (vrstvy), kde každé vrstvě („stratum“) může odpovídat jiný programovací jazyk v případě, že je zdrojový kód transformován z jednoho programovacího jazyka do jazyka jiného. Pokud je však testovací aplikace napsána přímo v Javě, nemusí nás problematika vrstev příliš trápit:

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

Zjištění jména zdrojového kódu, v němž je volaná metoda a samozřejmě i příslušná třída deklarována, je velmi jednoduché, protože pro tento účel lze použít volání com.sun.jdi.Location.sourceName(). V případě potřeby je možné zjistit i cestu k tomuto souboru s využitím com.sun.jdi.Location.sourcePath() (tuto informaci jsme v rozhraní JVM TI zjišťovali jen velmi složitým způsobem):

    /**
     * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci.
     */
    private static String getSourceName(Location location) {
        try {
            return location.sourceName();
        }
        catch (AbsentInformationException e) {
            return "unknown";
        }
    }

Použité metody rozhraní JDI:

# Třída/rozhraní Návratový typ Metoda
1 com.sun.jdi.Location int lineNumber()
2 com.sun.jdi.Location String sourceName()

6. Kompletní zdrojový kód demonstračního příkladu JDIStackTraceList

V předchozích kapitolách byly popsány ty nejdůležitější metody, které jsou součástí dnešního demonstračního příkladu nazvaného JDIStackTraceList. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto stále ještě 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.Location;
import com.sun.jdi.StackFrame;
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
 *
 * Po pripojeni se vypisou obsahy zasobnikovych ramcu vsech vlaken.
 *
 * @author Pavel Tisnovsky
 */
public class JDIStackTraceList {
 
    /**
     * 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);
 
            // spustit sledovany virtualni stroj po dobu jedne sekundy
            runVirtualMachineForOneSecond(virtualMachine);
 
            // 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;
    }
 
    /**
     * Spustit sledovany virtualni stroj po dobu jedne sekundy
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void runVirtualMachineForOneSecond(VirtualMachine virtualMachine) {
        virtualMachine.resume();
        try {
            Thread.sleep(1000);
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        }
        virtualMachine.suspend();
    }
 
    /**
     * 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:");
        printThreadInfo(virtualMachine);
    }
 
    /**
     * 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);
            printStackFrames(thread);
        }
        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 zasobnikovych ramcu
     *
     * @param thread JDI objekt predstavujici vlakno
     */
    private static void printStackFrames(ThreadReference thread) {
        try {
            System.out.format("%16s-------------------------------------------------\n", "");
            System.out.format("%16sStack frame count: %d\n", "", thread.frameCount());
            // vypsat informace o vsech zasobnikovych ramcich
            for (StackFrame frame : thread.frames()) {
                printStackFrameInfo(frame);
            }
            System.out.format("%16s-------------------------------------------------\n", "");
        }
        catch (IncompatibleThreadStateException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Vypis informaci o vybranem zasobnikovem ramci
     *
     * @param frame zasobnikovy ramec
     */
    private static void printStackFrameInfo(StackFrame frame) {
        Location location = frame.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 mame vsechny informace, lze je tedy vypsat
        System.out.format("%16s%s.%s (%s:%s)\n", "", className, methodName, sourceName, lineNumber);
    }
 
    /**
     * 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>";
    }
 
}

7. Spuštění demonstračního příkladu

Před vlastním spuštěním demonstračního příkladu JDIStackTraceList je nejprve nutné spustit samostatnou cílovou JVM i s testovanou aplikací. Tato aplikace je velmi jednoduchá, protože se po své inicializaci dostane do nekonečné smyčky, což nám samozřejmě vyhovuje, neboť bude jisté, že po cca jednosekundovém běhu cílové JVM (viz též druhou kapitolu) se bude hlavní vlákno aplikace skutečně nacházet uvnitř této smyčky. Povšimněte si, že nekonečné smyčky je dosaženo až po postupném volání main()run()foo()bar():

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

Překlad a spuštění této aplikace v cílovém virtuálním stroji Javy se provede následujícím způsobem:

javac Test2.java
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test2

Nyní již zbývá provést překlad a spuštění našeho demonstračního příkladu:

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

Po spuštění by se měl na standardním výstupu objevit přibližně tento text:

bitcoin školení listopad 24

Connecting to virtual machine
Connected
Basic virtual machine info:
Thread info:
    UniqueID    Thread name            Status       Suspended
          56    Attach Listener        running         yes
                -------------------------------------------------
                Stack frame count: 0
                -------------------------------------------------
          57    Signal Dispatcher      running         yes
                -------------------------------------------------
                Stack frame count: 0
                -------------------------------------------------
          58    Finalizer              Object.wait     yes
                -------------------------------------------------
                Stack frame count: 4
                java.lang.Object.wait (Object.java:<native method>)
                java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:116)
                java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:132)
                java.lang.ref.Finalizer$FinalizerThread.run (Finalizer.java:159)
                -------------------------------------------------
          59    Reference Handler      Object.wait     yes
                -------------------------------------------------
                Stack frame count: 3
                java.lang.Object.wait (Object.java:<native method>)
                java.lang.Object.wait (Object.java:485)
                java.lang.ref.Reference$ReferenceHandler.run (Reference.java:116)
                -------------------------------------------------
           1    main                   running         yes
                -------------------------------------------------
                Stack frame count: 15
                java.io.FileOutputStream.writeBytes (FileOutputStream.java:<native method>)
                java.io.FileOutputStream.write (FileOutputStream.java:260)
                java.io.BufferedOutputStream.flushBuffer (BufferedOutputStream.java:65)
                java.io.BufferedOutputStream.flush (BufferedOutputStream.java:123)
                java.io.PrintStream.write (PrintStream.java:432)
                sun.nio.cs.StreamEncoder.writeBytes (StreamEncoder.java:202)
                sun.nio.cs.StreamEncoder.implFlushBuffer (StreamEncoder.java:272)
                sun.nio.cs.StreamEncoder.flushBuffer (StreamEncoder.java:85)
                java.io.OutputStreamWriter.flushBuffer (OutputStreamWriter.java:168)
                java.io.PrintStream.newLine (PrintStream.java:496)
                java.io.PrintStream.println (PrintStream.java:757)
                Test2.bar (Test2.java:13)
                Test2.foo (Test2.java:8)
                Test2.run (Test2.java:4)
                Test2.main (Test2.java:18)
                -------------------------------------------------

Calling exit

Obsah jednotlivých zásobníkových rámců se může nepatrně lišit, ovšem minimálně v případě vlákna pojmenovaného main by se měla na jeho začátku objevit sekvence volání:

Test2.bar (Test2.java:13)
Test2.foo (Test2.java:8)
Test2.run (Test2.java:4)
Test2.main (Test2.java:18)

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

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

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