Pohled pod kapotu JVM – získání informací o třídách ve sledované JVM s využitím rozhraní JDI

Pavel Tišnovský 7. 5. 2013

V dnešní části seriálu o jazyku Java si ukážeme způsob využití rozhraní JDI (Java Debugger Interface) pro získání všech informací o třídách načtených do sledovaného (monitorovaného) virtuálního stroje Javy. Přes JDI lze získat například všechny metody i atributy vybrané třídy, informace o parametrech metod atd.

Obsah

1. Pohled pod kapotu JVM – získání informací o třídách ve sledované JVM s využitím rozhraní JDI

2. Získání seznamu všech tříd ve sledovaném virtuálním stroji

3. Výpis všech základních informací o třídě

4. Výpis informací o všech atributech třídy

5. Výpis informací o všech metodách třídy

6. Získání jmen a typů argumentů metod

7. Kompletní zdrojový kód demonstračního příkladu JDIAllClassesList

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

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

10. Odkazy na Internetu

1. Pohled pod kapotu JVM – získání informací o třídách ve sledované JVM s využitím rozhraní JDI

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy si na praktickém demonstračním příkladu ukážeme, jakým způsobem je možné s využitím rozhraní JDI (Java Debugger Interface) získat všechny důležité informace o třídách načtených do sledovaného (monitorovaného) virtuálního stroje Javy. Přes JDI je možné přečíst například všechny metody i atributy vybrané třídy, informace o parametrech metod atd. Tyto údaje jsou velmi důležité, protože je využijeme při programování skutečného (i když velmi jednoduchého) debuggeru.

Dnes popsané či 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.VirtualMachine List<ReferenceType> allClasses()
4 com.sun.jdi.ReferenceType String name()
5 com.sun.jdi.ReferenceType List<Field> allFields()
6 com.sun.jdi.ReferenceType List<Method> allMethods()
7 com.sun.jdi.Type String signature()
8 com.sun.jdi.ReferenceType String genericSignature()
9 com.sun.jdi.ReferenceType String sourceName()
10 com.sun.jdi.TypeComponent boolean isStatic()
11 com.sun.jdi.TypeComponent boolean isFinal()
12 com.sun.jdi.Field boolean isTransient()
13 com.sun.jdi.Field boolean isVolatile()
14 com.sun.jdi.Method boolean isNative()
15 com.sun.jdi.Method boolean isSynchronized()
16 com.sun.jdi.Accessible boolean isPublic()
17 com.sun.jdi.Accessible boolean isProtected()
18 com.sun.jdi.Accessible boolean isPrivate()
19 com.sun.jdi.Method List<LocalVariable> arguments()
20 com.sun.jdi.Method List<String> argumentTypeNames()
21 com.sun.jdi.LocalVariable String name()
22 com.sun.jdi.LocalVariable String typeName()

2. Získání seznamu všech tříd ve sledovaném virtuálním stroji

Zdrojové kódy dnešního demonstračního příkladu nazvaného JDIAllClassesList jsou z velké části založeny na zdrojových kódech příkladu JDIMethodVariables [1], který byl popsán v předchozí části tohoto seriálu. Tento program se nejprve pokusí o připojení ke sledovanému virtuálnímu stroji Javy s využitím konektoru používajícího socket na portu 6502 (číslo portu je použito při spouštění sledované JVM). Následně je sledovaná JVM spuštěna po dobu přibližně jedné sekundy a po jejím opětovném zastavení je již téměř jisté, že do virtuálního stroje byly načteny všechny třídy, které posléze budeme moci zkoumat přes rozhraní JDI.

V metodě printFilteredClassList(), která je součástí demonstračního příkladu, se nejprve s využitím metody VirtualMachine.allClasses() získá seznam všech tříd, které byly načteny do sledovaného virtuálního stroje Javy, a posléze se na základě jména každé třídy provede výběr pouze těch tříd, v jejichž názvu se vyskytují řetězce „String“ a „Test“. Tyto řetězce jsou uloženy v poli CLASS_NAME_FILTERS, takže je snadné je změnit, popř. upravit demonstrační příklad takovým způsobem, aby se tyto parametry načítaly například z příkazové řádky. Důležité je, že metoda VirtualMachine.allClasses() vrací seznam objektů typu ReferenceType, nikoli objektů typu Class. To je ovšem pochopitelné, protože přes JDI sice dokážeme získat všechny potřebné informace, ovšem například třída String může být ve sledované JVM zcela odlišná od třídy String použité v debuggeru/demonstračním příkladu. Obě aplikace například mohou běžet na jiných typech virtuálního stroje a dokonce i na jiném počítači:

    /**
     * Vypis vyfiltrovanych trid nactenych do sledovaneho virtualniho stroje.
     * Filtr se nastavuje pres {@link JDIAllClassesList#CLASS_NAME_FILTERS}
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printFilteredClassList(VirtualMachine virtualMachine) {
        List<ReferenceType> allClasses = virtualMachine.allClasses();
 
        // projit vsemi tridami, ktere se nachazeji ve sledovanem virtualnim stroji
        for (ReferenceType klass : allClasses) {
            String className = klass.name();
            // zjistit, zda se ma tato trida vyfiltrovat ci nikoli
            boolean filterClass = true;
            // jednoduchy filtr
            for (String filter : CLASS_NAME_FILTERS) {
                if (className.contains(filter)) {
                    filterClass = false;
                    break;
                }
            }
            // cast jmena tridy nebyla v CLASS_NAME_FILTERS nalezena
            if (filterClass) {
                continue;
            }
            printClassInfo(klass);
        }
    }

3. Výpis všech základních informací o třídě

V případě, že jméno třídy obsahuje řetězec „String“ nebo „Test“, je v debuggeru zavolána metoda printClassInfo() sloužící pro výpis téměř všech dostupných informací o nalezené třídě. Zdrojový kód této metody je velmi jednoduchý:

    /**
     * Vypis vsech relevantnich informaci o vybrane tride.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassInfo(ReferenceType klass) {
        printBasicClassInfo(klass);
        printClassFields(klass);
        printClassMethods(klass);
        printSeparator();
    }

Zajímavější je až zdrojový kód metody nazvané printBasicClassInfo(), v níž se zjišťují čtyři důležité údaje o prozkoumávané třídě. V první řadě se jedná o jméno třídy v podobě, v jaké je jméno použito (například jako identifikátor) v samotném programovacím jazyku Java. Dále se pak zjišťuje signatura třídy, jejíž formát jsme si popsali ve druhé části tohoto seriálu věnované popisu bajtkódu JVM. Kromě signatury třídy se přečte i její takzvaná obecná signatura, z níž je možné mj. získat seznam implementovaných rozhraní. O přečtení jména zdrojového souboru s definicí třídy se stará samostatná metoda nazvaná getClassSourceName(), která je popsána v dalším odstavci:

    /**
     * Zakladni informace o tride.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printBasicClassInfo(ReferenceType klass) {
        String className = klass.name();
        String classSignature = klass.signature();
        String genericSignature = klass.genericSignature();
        String sourceName = getClassSourceName(klass);
 
        System.out.println("Class " + className);
        System.out.println("    Signature:         " + classSignature);
        System.out.println("    Generic signature: " + (genericSignature == null ? "not present" : genericSignature));
        System.out.println("    Source:            " + sourceName);
    }

Poslední obecnou informací, která se zjišťuje pro zkoumanou třídu, je jméno zdrojového souboru s definicí třídy. Toto jméno nemusí být v některých případech dostupné, například tehdy, pokud se jedná o pole libovolného typu, tj. například o třídu String[] apod. V takovém případě se namísto skutečného jména zdrojového souboru vrátí řetězec „unknown“. V rozhraní JDI nelze přímo zjistit, zda je jméno zdrojového souboru dostupné, jinak, než zachycením výjimky:

    /**
     * Zjisteni jmena zdrojoveho souboru.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     * @return jmeno zdrojoveho souboru
     */
    private static String getClassSourceName(ReferenceType klass) {
        String sourceName;
        try {
            sourceName = klass.sourceName();
        }
        catch (AbsentInformationException e) {
            sourceName = "unknown";
        }
        return sourceName;
    }

4. Výpis informací o všech atributech třídy

Další důležitou a zajímavou metodou použitou v dnešním demonstračním příkladu je metoda s názvem printClassFields(), která slouží k výpisu informací o všech atributech zkoumané třídy. Atributy jsou získány s využitím metody com.sun.jdi.ReferenceType.allFields() vracející seznam objektů typu com.sun.jdi.Field. Tyto objekty mají společné rozhraní com.sun.jdi.TypeComponent a com.sun.jdi.Accessible s objekty typu com.sun.jdi.Method, čehož využijeme později. Jaké informace však lze o atributech třídy získat? Jedná se samozřejmě o jméno atributu, dále pak o typ atributu (popř. v našem případě o jméno typu), a v neposlední řadě taktéž o prakticky všechny modifikátory atributu – static, final, transient, volatile – i o přístupová práva k atributům – public, protected, private a (package protected):

    /**
     * Vypis informaci o vsech atributech tridy.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassFields(ReferenceType klass) {
        System.out.println("    Fields:");
        List<Field> fields = klass.allFields();
 
        // projit vsemi atributy
        for (Field field : fields) {
            String name = field.name();
            String type = field.typeName();
            String accessibilityStr = getAccessibility(field);
            String staticStr = field.isStatic() ? "static " : "";
            String finalStr = field.isFinal() ? "final " : "";
            String transientStr = field.isTransient() ? "transient " : "";
            String volatileStr = field.isVolatile() ? "volatile " : "";
 
            System.out.format("        %s%s%s%s%s%s %s; \n",
                    accessibilityStr, staticStr, finalStr,
                    transientStr, volatileStr, type, name);
        }
    }

Mimochodem – je zajímavé, jak modifikátory a přístupová práva atributů získaná přes rozhraní JDI korespondují s informacemi o atributech uložených přímo v bajtkódu dané třídy, což bylo téma podrobněji popsané ve čtvrté části tohoto seriálu v kapitolách 3 a 4:

# Název masky Hodnota Význam, pokud je maska nastavena
1 ACC_PUBLIC 0×0001 veřejná datová položka dostupná vně třídy i vně balíčku
2 ACC_PRIVATE 0×0002 privátní datová položka dostupná pouze uvnitř třídy
3 ACC_PROTECTED 0×0004 chráněná datová položka dostupná uvnitř třídy, z případných odvozených tříd i v rámci celého balíčku
4 ACC_STATIC 0×0008 statická datová položka, může se kombinovat s předchozí trojicí příznaků
5 ACC_FINAL 0×0010 finální datová položka
6 ACC_VOLATILE 0×0040 příznak volatile – hodnota se vždy přečte či zapíše do paměti (nezůstane uložena pouze v registrech)
7 ACC_TRANSIENT 0×0080 příznak transient – nebude se zpracovávat při (de)serializaci objektu
8 ACC_SYNTHETIC 0×1000 syntetická datová položka, která nemá svůj protějšek ve zdrojovém kódu (je generována překladačem)
9 ACC_ENUM 0×4000 jedná se o prvek výčtového typu

5. Výpis informací o všech metodách třídy

Podobným způsobem, jakým jsme získali informace o všech atributech sledované třídy, je možné přečíst i informace o jejích metodách. S využitím rozhraní JDI lze přečíst jméno metody, její návratový typ (popř. jméno návratového typu, což je pro nás jednodušší a především dostačující), modifikátory metody (static, final, native, synchronized) a samozřejmě i modifikátory přístupu k metodě, tj. public, private, protected a nepřímo i package protected (implicitní modifikátor). Samostatně je implementován výpis všech parametrů metody, což je téma podrobněji popsané až v šesté kapitole:

    /**
     * Vypis informaci o vsech metodach tridy.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassMethods(ReferenceType klass) {
        System.out.println("    Methods:");
        List<Method> methods = klass.allMethods();
        // projit vsemi metodami
        for (Method method : methods) {
            String name = method.name();
            String type = method.returnTypeName();
            String accessibilityStr = getAccessibility(method);
            String staticStr = method.isStatic() ? "static " : "";
            String finalStr = method.isFinal() ? "final " : "";
            String nativeStr = method.isNative() ? "native " : "";
            String synchronizedStr = method.isSynchronized() ? "synchronized " : "";
            System.out.format("        %s%s%s%s%s%s %s",
                    accessibilityStr, staticStr, finalStr,
                    nativeStr, synchronizedStr, type, name);
            printMethodArguments(method);
        }
    }

V metodách printClassFields()printClassMethods() implementovaných v dnešním demonstračním příkladu bylo mj. nutné zjistit i modifikátory přístupu k metodám a k atributům zkoumané třídy. Vzhledem k tomu, že metody zkoumané třídy jsou v debuggeru reprezentovány objekty typu com.sun.jdi.Method a atributy jsou reprezentovány objekty typu com.sun.jdi.Field, které mají společné rozhraní com.sun.jdi.Accessible, je implementace metody getAccessibility() (vracející textovou podobu modifikátorů) velmi jednoduchá a hlavně společná jak pro atributy, tak i pro metody sledované třídy:

    /**
     * Pristupova prava k atributu ci k metode.
     */
    private static String getAccessibility(Accessible methodOrField) {
        if (methodOrField.isPublic()) {
            return "public ";
        }
        if (methodOrField.isProtected()) {
            return "protected ";
        }
        if (methodOrField.isPrivate()) {
            return "private ";
        }
        return "";
    }

6. Získání jmen a typů argumentů metod

U metod zkoumané třídy je nutné zjistit a vypsat ještě jednu důležitou informaci. Jedná se o seznam argumentů metody. V závislosti na tom, zda byl bajtkód zkoumané třídy přeložen s přepínačem -g či bez tohoto modifikátoru, lze o argumentech zjistit buď pouze informaci o typech jednotlivých argumentů, nebo i jména argumentů – v tomto případě je však nutné provést překlad s přepínačem -g, jinak se tato informace do bajtkódu neuloží, protože ji virtuální stroj Javy ve skutečnosti nepotřebuje. S využitím rozhraní JDI nelze snadno zjistit, které informace o argumentech metod jsou k dispozici, proto je kód metody nazvané printMethodArguments() rozdělen na dvě části. V první části umístěné v bloku try {} se zjišťují jak jména, tak i typy argumentů, v části umístěné v bloku catch {} pak pouze typy argumentů metody, neboť tyto informace má JVM k dispozici vždy. Výjimka, která rozhodne o použití prvního či druhého bloku, je vyhozena z metody Method.arguments() umístěné jako první příkaz v bloku try{}:

    /**
     * Vypis argumentu metody.
     *
     * @param method metoda ve sledovanem virtualnim stroji
     */
    private static void printMethodArguments(Method method) {
        System.out.print("(");
        boolean first = true;
 
        try {
            // idealni je zjistit jmena i typy argumentu
            List<LocalVariable> arguments = method.arguments();
 
            // vypsat typy a jmena vsech argumentu metody
            for (LocalVariable argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument.typeName() + " " + argument.name());
            }
        }
        catch (AbsentInformationException e) {
            // pokud jmena argumentu nelze zjistit, jejich typy jsou vzdy k dispozici
            List<String> arguments = method.argumentTypeNames();
 
            // vypsat typy vsech argumentu metody
            for (String argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument);
            }
        }
        System.out.println(");");
    }

7. Kompletní zdrojový kód demonstračního příkladu JDIAllClassesList

V předchozích kapitolách byly popsány ty nejdůležitější a nejzajímavější metody, které jsou součástí dnešního demonstračního příkladu nazvaného JDIAllClassesList. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto příkladu; jeho originální verzi pak lze najít v Mercurial repositáři popsaném v deváté kapitole:

import java.io.IOException;
import java.util.List;
import java.util.Map;
 
import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.Accessible;
import com.sun.jdi.Bootstrap;
import com.sun.jdi.Field;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Method;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
 
 
 
/**
 * 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 vsechny tridy nactene do sledovaneho virtualniho stroje,
 * pricemz jmena trid (ktere maji byt vypsany) jsou vyfiltrovany na zaklade retezcu
 * ulozenych v konstante {@link JDIAllClassesList#CLASS_NAME_FILTERS}.
 *
 * @author Pavel Tisnovsky
 */
public class JDIAllClassesList {
 
    /**
     * 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";
 
    /**
     * Filtr, pomoci nehoz jsou vybrany pouze tridy urciteho jmena.
     */
    private static final String[] CLASS_NAME_FILTERS = {
        "String",
        "Test"
    };
 
    /**
     * 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;
        }
 
        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 po dobu jedne sekundy
            runVirtualMachineForOneSecond(virtualMachine);
 
            // vypis vsech vybranych trid nactenych ve sledovanem virtualnim stroji
            printFilteredClassList(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 vyfiltrovanych trid nactenych do sledovaneho virtualniho stroje.
     * Filtr se nastavuje pres {@link JDIAllClassesList#CLASS_NAME_FILTERS}
     *
     * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen
     */
    private static void printFilteredClassList(VirtualMachine virtualMachine) {
        List<ReferenceType> allClasses = virtualMachine.allClasses();
 
        // projit vsemi tridami, ktere se nachazi ve sledovanem virtualnim stroji
        for (ReferenceType klass : allClasses) {
            String className = klass.name();
            // zjistit, zda se ma tato trida vyfiltrovat ci nikoli
            boolean filterClass = true;
            // jednoduchy filtr
            for (String filter : CLASS_NAME_FILTERS) {
                if (className.contains(filter)) {
                    filterClass = false;
                    break;
                }
            }
            // cast jmena tridy nebyla v CLASS_NAME_FILTERS nalezena
            if (filterClass) {
                continue;
            }
            printClassInfo(klass);
        }
    }
 
    /**
     * Vypis vsech relevantnich informaci o vybrane tride.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassInfo(ReferenceType klass) {
        printBasicClassInfo(klass);
        printClassFields(klass);
        printClassMethods(klass);
        printSeparator();
    }
 
    /**
     * <hr>
     */
    private static void printSeparator() {
        System.out.println("-------------------------------------------------------");
    }
 
    /**
     * Zakladni informace o tride.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printBasicClassInfo(ReferenceType klass) {
        String className = klass.name();
        String classSignature = klass.signature();
        String genericSignature = klass.genericSignature();
        String sourceName = getClassSourceName(klass);
 
        System.out.println("Class " + className);
        System.out.println("    Signature:         " + classSignature);
        System.out.println("    Generic signature: " + (genericSignature == null ? "not present" : genericSignature));
        System.out.println("    Source:            " + sourceName);
    }
 
    /**
     * Zjisteni jmena zdrojoveho souboru.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     * @return jmeno zdrojoveho souboru
     */
    private static String getClassSourceName(ReferenceType klass) {
        String sourceName;
        try {
            sourceName = klass.sourceName();
        }
        catch (AbsentInformationException e) {
            sourceName = "unknown";
        }
        return sourceName;
    }
 
    /**
     * Vypis informaci o vsech atributech tridy.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassFields(ReferenceType klass) {
        System.out.println("    Fields:");
        List<Field> fields = klass.allFields();
 
        // projit vsemi atributy
        for (Field field : fields) {
            String name = field.name();
            String type = field.typeName();
            String accessibilityStr = getAccessibility(field);
            String staticStr = field.isStatic() ? "static " : "";
            String finalStr = field.isFinal() ? "final " : "";
            String transientStr = field.isTransient() ? "transient " : "";
            String volatileStr = field.isVolatile() ? "volatile " : "";
 
            System.out.format("        %s%s%s%s%s%s %s; \n",
                    accessibilityStr, staticStr, finalStr,
                    transientStr, volatileStr, type, name);
        }
    }
 
    /**
     * Vypis informaci o vsech metodach tridy.
     *
     * @param klass trida ve sledovanem virtualnim stroji Javy
     */
    private static void printClassMethods(ReferenceType klass) {
        System.out.println("    Methods:");
        List<Method> methods = klass.allMethods();
        // projit vsemi metodami
        for (Method method : methods) {
            String name = method.name();
            String type = method.returnTypeName();
            String accessibilityStr = getAccessibility(method);
            String staticStr = method.isStatic() ? "static " : "";
            String finalStr = method.isFinal() ? "final " : "";
            String nativeStr = method.isNative() ? "native " : "";
            String synchronizedStr = method.isSynchronized() ? "synchronized " : "";
            System.out.format("        %s%s%s%s%s%s %s",
                    accessibilityStr, staticStr, finalStr,
                    nativeStr, synchronizedStr, type, name);
            printMethodArguments(method);
        }
    }
 
    /**
     * Vypis argumentu metody.
     *
     * @param method metoda ve sledovanem virtualnim stroji
     */
    private static void printMethodArguments(Method method) {
        System.out.print("(");
        boolean first = true;
 
        try {
            // idealni je zjistit jmena i typy argumentu
            List<LocalVariable> arguments = method.arguments();
 
            // vypsat typy a jmena vsech argumentu metody
            for (LocalVariable argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument.typeName() + " " + argument.name());
            }
        }
        catch (AbsentInformationException e) {
            // pokud jmena argumentu nelze zjistit, jejich typy jsou vzdy k dispozici
            List<String> arguments = method.argumentTypeNames();
 
            // vypsat typy vsech argumentu metody
            for (String argument : arguments) {
                if (first) {
                    first = false;
                }
                else {
                    System.out.print(", ");
                }
                System.out.print(argument);
            }
        }
        System.out.println(");");
    }
 
    /**
     * Pristupova prava k atributu ci k metode.
     */
    private static String getAccessibility(Accessible methodOrField) {
        if (methodOrField.isPublic()) {
            return "public ";
        }
        if (methodOrField.isProtected()) {
            return "protected ";
        }
        if (methodOrField.isPrivate()) {
            return "private ";
        }
        return "";
    }
 
}

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

Dnešní demonstrační příklad si vyzkoušíme spustit proti virtuálnímu stroji, v němž poběží testovací aplikace Test3, kterou jsme použili již minule. 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 (důležité je zde především číslo portu, k němuž se následně připojíme s využitím rozhraní JDI):

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

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

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

Po spuštění by se měl na standardním výstupu objevit přibližně následující text obsahující metody a atributy vybraných tříd. Povšimněte si, že kvůli překladu třídy Test3 bez použití přepínače -g nezískáme jména argumentů metod, ale pouze jejich typy:

Connecting to virtual machine
Connected
Class Test3
    Signature:         LTest3;
    Generic signature: not present
    Source:            Test3.java
    Fields:
    Methods:
        public void <init>();
        public void run(int, char);
        public void foo(float);
        public void bar(float, float[], java.lang.String);
        public static void main(java.lang.String[]);
        public void <init>();
        private static native void registerNatives();
        public final native java.lang.Class getClass();
        public native int hashCode();
        public boolean equals(java.lang.Object);
        protected native java.lang.Object clone();
        public java.lang.String toString();
        public final native void notify();
        public final native void notifyAll();
        public final native void wait(long);
        public final void wait(long, int);
        public final void wait();
        protected void finalize();
        static void <clinit>();
-------------------------------------------------------
Class java.lang.String
    Signature:         Ljava/lang/String;
    Generic signature: Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/lang/CharSequence;
    Source:            String.java
    Fields:
        private final char[] value;
        private final int offset;
        private final int count;
        private int hash;
        private static final long serialVersionUID;
        private static final java.io.ObjectStreamField[] serialPersistentFields;
        public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
    Methods:
        public void <init>();
        public void <init>(java.lang.String);
        public void <init>(char[]);
        public void <init>(char[], int, int);
        public void <init>(int[], int, int);
        public void <init>(byte[], int, int, int);
        public void <init>(byte[], int);
        private static void checkBounds(byte[], int, int);
        public void <init>(byte[], int, int, java.lang.String);
        public void <init>(byte[], int, int, java.nio.charset.Charset);
        public void <init>(byte[], java.lang.String);
        public void <init>(byte[], java.nio.charset.Charset);
        public void <init>(byte[], int, int);
        public void <init>(byte[]);
        public void <init>(java.lang.StringBuffer);
        public void <init>(java.lang.StringBuilder);
        void <init>(int, int, char[]);
        public int length();
        public boolean isEmpty();
        public char charAt(int);
        public int codePointAt(int);
        public int codePointBefore(int);
        public int codePointCount(int, int);
        public int offsetByCodePoints(int, int);
        void getChars(char[], int);
        public void getChars(int, int, char[], int);
        public void getBytes(int, int, byte[], int);
        public byte[] getBytes(java.lang.String);
        public byte[] getBytes(java.nio.charset.Charset);
        public byte[] getBytes();
        public boolean equals(java.lang.Object);
        public boolean contentEquals(java.lang.StringBuffer);
        public boolean contentEquals(java.lang.CharSequence);
        public boolean equalsIgnoreCase(java.lang.String);
        public int compareTo(java.lang.String);
        public int compareToIgnoreCase(java.lang.String);
        public boolean regionMatches(int, java.lang.String, int, int);
        public boolean regionMatches(boolean, int, java.lang.String, int, int);
        public boolean startsWith(java.lang.String, int);
        public boolean startsWith(java.lang.String);
        public boolean endsWith(java.lang.String);
        public int hashCode();
        public int indexOf(int);
        public int indexOf(int, int);
        public int lastIndexOf(int);
        public int lastIndexOf(int, int);
        public int indexOf(java.lang.String);
        public int indexOf(java.lang.String, int);
        static int indexOf(char[], int, int, char[], int, int, int);
        public int lastIndexOf(java.lang.String);
        public int lastIndexOf(java.lang.String, int);
        static int lastIndexOf(char[], int, int, char[], int, int, int);
        public java.lang.String substring(int);
        public java.lang.String substring(int, int);
        public java.lang.CharSequence subSequence(int, int);
        public java.lang.String concat(java.lang.String);
        public java.lang.String replace(char, char);
        public boolean matches(java.lang.String);
        public boolean contains(java.lang.CharSequence);
        public java.lang.String replaceFirst(java.lang.String, java.lang.String);
        public java.lang.String replaceAll(java.lang.String, java.lang.String);
        public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence);
        public java.lang.String[] split(java.lang.String, int);
        public java.lang.String[] split(java.lang.String);
        public java.lang.String toLowerCase(java.util.Locale);
        public java.lang.String toLowerCase();
        public java.lang.String toUpperCase(java.util.Locale);
        public java.lang.String toUpperCase();
        public java.lang.String trim();
        public java.lang.String toString();
        public char[] toCharArray();
        public static java.lang.String format(java.lang.String, java.lang.Object[]);
        public static java.lang.String format(java.util.Locale, java.lang.String, java.lang.Object[]);
        public static java.lang.String valueOf(java.lang.Object);
        public static java.lang.String valueOf(char[]);
        public static java.lang.String valueOf(char[], int, int);
        public static java.lang.String copyValueOf(char[], int, int);
        public static java.lang.String copyValueOf(char[]);
        public static java.lang.String valueOf(boolean);
        public static java.lang.String valueOf(char);
        public static java.lang.String valueOf(int);
        public static java.lang.String valueOf(long);
        public static java.lang.String valueOf(float);
        public static java.lang.String valueOf(double);
        public native java.lang.String intern();
        public int compareTo(java.lang.Object);
        static void <clinit>();
        public void <init>();
        private static native void registerNatives();
        public final native java.lang.Class getClass();
        public native int hashCode();
        public boolean equals(java.lang.Object);
        protected native java.lang.Object clone();
        public java.lang.String toString();
        public final native void notify();
        public final native void notifyAll();
        public final native void wait(long);
        public final void wait(long, int);
        public final void wait();
        protected void finalize();
        static void <clinit>();
        public int compareTo(java.lang.Object);
        public int length();
        public char charAt(int);
        public java.lang.CharSequence subSequence(int, int);
        public java.lang.String toString();
-------------------------------------------------------

V případě, že se testovací třída přeloží i s ladicími informacemi

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

získáme po spuštění demonstračního příkladu

widgety

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

výstup, v němž budou zobrazena i jména argumentů metod testovací třídy:

Connecting to virtual machine
Connected
Class Test3
    Signature:         LTest3;
    Generic signature: not present
    Source:            Test3.java
    Fields:
    Methods:
        public void <init>();
        public void run(int value, char znak);
        public void foo(float value);
        public void bar(float value, float[] array, java.lang.String message);
        public static void main(java.lang.String[] args);
        public void <init>();
        private static native void registerNatives();
        public final native java.lang.Class getClass();
        public native int hashCode();
        public boolean equals(java.lang.Object);
        protected native java.lang.Object clone();
        public java.lang.String toString();
        public final native void notify();
        public final native void notifyAll();
        public final native void wait(long);
        public final void wait(long, int);
        public final void wait();
        protected void finalize();
        static void <clinit>();
-------------------------------------------------------
Class java.lang.String
    Signature:         Ljava/lang/String;
    Generic signature: Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/lang/CharSequence;
    Source:            String.java
    Fields:
        private final char[] value;
        private final int offset;
        private final int count;
        private int hash;
        private static final long serialVersionUID;
        private static final java.io.ObjectStreamField[] serialPersistentFields;
        public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
    Methods:
        public void <init>();
        public void <init>(java.lang.String);
        public void <init>(char[]);
        public void <init>(char[], int, int);
        public void <init>(int[], int, int);
        public void <init>(byte[], int, int, int);
        public void <init>(byte[], int);
        private static void checkBounds(byte[], int, int);
        public void <init>(byte[], int, int, java.lang.String);
        public void <init>(byte[], int, int, java.nio.charset.Charset);
        public void <init>(byte[], java.lang.String);
        public void <init>(byte[], java.nio.charset.Charset);
        public void <init>(byte[], int, int);
        public void <init>(byte[]);
        public void <init>(java.lang.StringBuffer);
        public void <init>(java.lang.StringBuilder);
        void <init>(int, int, char[]);
        public int length();
        public boolean isEmpty();
        public char charAt(int);
        public int codePointAt(int);
        public int codePointBefore(int);
        public int codePointCount(int, int);
        public int offsetByCodePoints(int, int);
        void getChars(char[], int);
        public void getChars(int, int, char[], int);
        public void getBytes(int, int, byte[], int);
        public byte[] getBytes(java.lang.String);
        public byte[] getBytes(java.nio.charset.Charset);
        public byte[] getBytes();
        public boolean equals(java.lang.Object);
        public boolean contentEquals(java.lang.StringBuffer);
        public boolean contentEquals(java.lang.CharSequence);
        public boolean equalsIgnoreCase(java.lang.String);
        public int compareTo(java.lang.String);
        public int compareToIgnoreCase(java.lang.String);
        public boolean regionMatches(int, java.lang.String, int, int);
        public boolean regionMatches(boolean, int, java.lang.String, int, int);
        public boolean startsWith(java.lang.String, int);
        public boolean startsWith(java.lang.String);
        public boolean endsWith(java.lang.String);
        public int hashCode();
        public int indexOf(int);
        public int indexOf(int, int);
        public int lastIndexOf(int);
        public int lastIndexOf(int, int);
        public int indexOf(java.lang.String);
        public int indexOf(java.lang.String, int);
        static int indexOf(char[], int, int, char[], int, int, int);
        public int lastIndexOf(java.lang.String);
        public int lastIndexOf(java.lang.String, int);
        static int lastIndexOf(char[], int, int, char[], int, int, int);
        public java.lang.String substring(int);
        public java.lang.String substring(int, int);
        public java.lang.CharSequence subSequence(int, int);
        public java.lang.String concat(java.lang.String);
        public java.lang.String replace(char, char);
        public boolean matches(java.lang.String);
        public boolean contains(java.lang.CharSequence);
        public java.lang.String replaceFirst(java.lang.String, java.lang.String);
        public java.lang.String replaceAll(java.lang.String, java.lang.String);
        public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence);
        public java.lang.String[] split(java.lang.String, int);
        public java.lang.String[] split(java.lang.String);
        public java.lang.String toLowerCase(java.util.Locale);
        public java.lang.String toLowerCase();
        public java.lang.String toUpperCase(java.util.Locale);
        public java.lang.String toUpperCase();
        public java.lang.String trim();
        public java.lang.String toString();
        public char[] toCharArray();
        public static java.lang.String format(java.lang.String, java.lang.Object[]);
        public static java.lang.String format(java.util.Locale, java.lang.String, java.lang.Object[]);
        public static java.lang.String valueOf(java.lang.Object);
        public static java.lang.String valueOf(char[]);
        public static java.lang.String valueOf(char[], int, int);
        public static java.lang.String copyValueOf(char[], int, int);
        public static java.lang.String copyValueOf(char[]);
        public static java.lang.String valueOf(boolean);
        public static java.lang.String valueOf(char);
        public static java.lang.String valueOf(int);
        public static java.lang.String valueOf(long);
        public static java.lang.String valueOf(float);
        public static java.lang.String valueOf(double);
        public native java.lang.String intern();
        public int compareTo(java.lang.Object);
        static void <clinit>();
        public void <init>();
        private static native void registerNatives();
        public final native java.lang.Class getClass();
        public native int hashCode();
        public boolean equals(java.lang.Object);
        protected native java.lang.Object clone();
        public java.lang.String toString();
        public final native void notify();
        public final native void notifyAll();
        public final native void wait(long);
        public final void wait(long, int);
        public final void wait();
        protected void finalize();
        static void <clinit>();
        public int compareTo(java.lang.Object);
        public int length();
        public char charAt(int);
        public java.lang.CharSequence subSequence(int, int);
        public java.lang.String toString();
-------------------------------------------------------

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

Zdrojové kódy dnešního demonstračního příkladu JDIAllClassesList, testovací třídy Test3 i skriptů použitých pro 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/. Navíc byly do repositáře dodány i výsledky běhu tohoto příkladu oproti třídě Test3 přeložené bez přidání ladicích informací i s ladicími informacemi (jedná se o soubory JDIAllClassesList_debug.txt a JDIAllClassesList_nodebug.txt). Prozatím nejnovější verze všech zmíněných zdrojových souborů a skriptů můžete najít na adresách:

10. 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?
Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

Podnikatel.cz: Insolvence LevneElektro.cz? Začíná boj o peníze

Insolvence LevneElektro.cz? Začíná boj o peníze

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

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

Nova opět stahuje „milionáře“

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Podnikatel.cz: Chystá se smršť legislativních novinek

Chystá se smršť legislativních novinek

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

Udělali jsme velkou chybu, napsal Čupr

Vitalia.cz: 5 důvodů, proč jet na výlov rybníka

5 důvodů, proč jet na výlov rybníka

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

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

EET pro e-shopy? Postavené na hlavu

120na80.cz: Hrbatá prsa aneb mýty o implantátech

Hrbatá prsa aneb mýty o implantátech

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

Kalousek chce odklad EET. Předvolební tah?

DigiZone.cz: Wimbledon na Nova Sport až do 2019

Wimbledon na Nova Sport až do 2019

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Lupa.cz: Hackeři mají data z půlmiliardy účtů Yahoo

Hackeři mají data z půlmiliardy účtů Yahoo

120na80.cz: Co je padesátkrát sladší než cukr?

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

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích

DigiZone.cz: Test: brýle pro virtuální realitu Exos Urban

Test: brýle pro virtuální realitu Exos Urban

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

Znáte už 5 novinek k #EET