Pohled pod kapotu JVM – spuštění a monitorování virtuálního stroje Javy s využitím rozhraní JDI

Pavel Tišnovský 2. 4. 2013

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již podruhé budeme zabývat rozhraním JDI (Java Debugger Interface). Ukážeme si, jak lze přes JDI spustit a následně i sledovat JVM a především aplikace spuštěné v tomto virtuálním stroji – stejným způsobem pracují i debuggery.

Obsah

1. Krátké shrnutí předchozí části seriálu: konektory v rozhraní JDI

2. Tři typy konektorů nabízených rozhraním JDI

3. Základ JDI: třída com.sun.jdi.Bootstrap a rozhraní com.sun.jdi.VirtualMachineManager

4. Metody předepsané rozhraním com.sun.jdi.connect.Launchin­gConnector

5. První demonstrační příklad – výpis všech argumentů výchozího konektoru typu LaunchingConnector

6. Spuštění nového virtuálního stroje Javy s využitím výchozího konektoru typu LaunchingConnector

7. Specifikace třídy, jejíž metoda main() se má v nově vytvořeném virtuálním stroji spustit

8. Přečtení standardního výstupu spuštěného virtuálního stroje Javy

9. Druhý demonstrační příklad – spuštění nové JVM s přečtením výstupu testovací aplikace

10. Odkazy na Internetu

1. Krátké shrnutí předchozí části seriálu: konektory v rozhraní JDI

V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy bude pokračovat v popisu javovského rozhraní JDI (Java Debugger Interface), které slouží, jak již název tohoto rozhraní napovídá, k implementaci různých ladicích nástrojů spolupracujících s virtuálním strojem Javy i s aplikacemi, které jsou v rámci tohoto virtuálního stroje spuštěny. Minule jsme si řekli základní informace o tomto rozhraní i o začlenění JDI do architektury nazvané JPDA (Java Platform Debugger Architecture). Připomeňme si, že JDI vývojářům debuggerů nabízí hned několik možností propojení mezi debuggerem a cílovým JVM, na němž je spuštěna laděná aplikace. O propojení mezi JDI a cílovou JVM se z hlediska programátora starají takzvané „konektory“, kterých existuje hned několik typů v závislosti na tom, jakým způsobem je debugger přes JNI k cílové JVM připojován:

# Konektor Plné jméno (identifikace) Popis
1 CommandLineLaunch „com.sun.jdi.CommandLineLaunch“ cílová JVM je spuštěna přímo přes JDI a ihned poté se naváže spojení přes socket či sdílenou paměť
2 RawCommandLineLaunch „com.sun.jdi.RawCommandLineLaunch“ podobné předchozímu konektoru, ovšem příkaz pro spuštění JVM se předává přes jediný řetězec (liší se jen způsob spuštění)
3 SocketAttach „com.sun.jdi.SocketAttach“ připojení k již běžící cílové JVM přes socket
4 SharedMemoryAttach „com.sun.jdi.SharedMemoryAttach“ připojení k již běžící cílové JVM přes sdílenou paměť
5 SocketListen „com.sun.jdi.SocketListen“ připojení k běžící cílové JVM na základě požadavku přijatého od této JVM
6 SharedMemoryListen „com.sun.jdi.SharedMemoryListen“ dtto, ale využije se sdílená paměť
7 ProcessAttach „com.sun.jdi.ProcessAttach“ připojení k již běžící cílové JVM, která je spuštěna s parametrem agentlib:jdwp=server=y

Obrázek 1: Vzájemný vztah mezi již popsaným (céčkovým) rozhraním JVM TI, protokolem JDWP a javovským rozhraním JDI. JVM TI, JDWP i JDI jsou součástí architektury JPDA – Java Platform Debugger Architecture.

2. Tři typy konektorů nabízených rozhraním JDI

Ve skutečnosti může na některých počítačových architekturách existovat i větší množství konektorů, takže tabulka uvedená v předchozí kapitole není úplná. Ovšem důležité je, že nezávisle na tom, kolik konektorů na dané počítačové architektuře existuje, je vždy můžeme rozdělit do tří skupin. První skupina konektorů implementuje rozhraní LaunchingConnector a je typická tím, že konektor sám spustí cílovou JVM i s laděnou aplikací. Typicky se jedná o konektor velmi často používaný různými integrovanými vývojovými prostředími pro spuštění a následné ladění aplikace. Druhá skupina konektorů implementuje rozhraní AttachingConnector. Jedná se o konektory, které se dokážou připojit k již běžící JVM. Tato JVM musí být spuštěna s volbou:

agentlib:jdwp=transport=typ_konektoru,server=y

Třetí typ konektorů implementuje rozhraní ListeningConnector. Tyto konektory slouží k tomu, aby se cílová JVM sama připojila k debuggeru, který již musí být spuštěn. Cílový virtuální stroj Javy bývá v tomto případě spuštěn s volbou:

agentlib:jdwp=transport=typ_konektoru,address=zvolena_adresa

Kde se namísto řetězce zvolena adresa zadá skutečná adresa, na které „poslouchá“ debugger.

Všechny tři skupiny konektorů jsou pro větší přehled vypsány v další tabulce i s příslušnými implementacemi:

# Typ konektoru Metoda propojení debuggeru a JVM Implementace
1 LaunchingConnector debugger spouští JVM com.sun.jdi.CommandLineLaunch, com.sun.jdi.RawCommandLineLaunch
2 AttachingConnector debugger se připojuje k běžící JVM com.sun.jdi.SocketAttach, com.sun.jdi.SharedMemoryAttach, com.sun.jdi.ProcessAttach
3 ListeningConnector debugger akceptuje žádost JVM o připojení com.sun.jdi.SocketListen, com.sun.jdi.SharedMemoryListen

Obrázek 2: (zopakování z minula) s využitím protokolu JDWP může s běžící (cílovou) JVM komunikovat prakticky libovolný proces; nemusí se tedy ve všech případech jednat o aplikaci naprogramovanou v Javě.

3. Základ JDI: třída com.sun.jdi.Bootstrap a rozhraní com.sun.jdi.VirtualMachineManager

Při použití rozhraní JDI se hned v počátečních fázích vývoje jednoduchého debuggeru setkáme s třídou nazvanou com.sun.jdi.Bootstrap a s rozhraním com.sun.jdi.Interface VirtualMachineManager (v názvech používám i jména balíčků, protože se NEjedná o třídy a rozhraní ze standardního Java SE API). Třída com.sun.jdi.Bootstrap je ve skutečnosti velmi jednoduchá, protože obsahuje pouze jednu metodu nazvanou virtualMachineManager. Tato metoda vrací jedinou instanci (singleton) třídy implementující rozhraní com.sun.jdi.Interface VirtualMachineManager (pozor, existuje větší množství tříd se stejným jménem, ovšem ležící v jiném balíčku):

com.sun.jdi.Bootstrap
public static VirtualMachineManager virtualMachineManager()

Rozhraní com.sun.jdi.Interface VirtualMachineManager je již pro vývojáře zajímavější, protože třídy, které toto rozhraní implementují, musí obsahovat mj. i následujících deset metod:

# Metoda Popis
1 int majorInterfaceVersion() vrací majoritní číslo verze rozhraní JDI
2 int minorInterfaceVersion() vrací minoritní číslo verze rozhraní JDI
     
3 LaunchingConnector defaultConnector() vrací výchozí konektor typu LaunchingConnector
4 List<LaunchingConnector> launchingConnectors() vrací všechny dostupné (implementované) konektory typu LaunchingConnector
5 List<ListeningConnector> listeningConnectors() vrací všechny dostupné (implementované) konektory typu ListeningConnector
6 List<AttachingConnector> attachingConnectors() vrací všechny dostupné (implementované) konektory typu AttachingConnector
7 List<Connector> allConnectors() vrací všechny dostupné (implementované) konektory – kombinace předchozích metod
     
8 VirtualMachine createVirtualMachine(Connection connection) vytvoření nové instance virtuálního stroje
9 VirtualMachine createVirtualMachine(Connection connection, Process process) vytvoření nové instance virtuálního stroje
10 List<VirtualMachine>connec­tedVirtualMachines() vrací seznam všech JVM připojených k debuggeru

Tyto metody lze rozdělit do tří skupin. Metody z první skupiny jsou velmi jednoduché, protože pouze slouží k vrácení majoritního a minoritního čísla verze rozhraní JDI. Ve druhé skupině se nachází metody vracející instanci jednoho konektoru, popř. seznam všech konektorů daného typu; ostatně s metodou allConnectors() jsme se již setkali minule. Ve třetí skupině metod najdeme metody sloužící ke spuštění nového virtuálního stroje Javy, popř. pro zjištění všech takto spuštěných JVM. Tyto metody se většinou nepoužívají přímo, protože pro spuštění virtuálního stroje se používá spíše metoda Connector.launch(), kterou si popíšeme v dalším textu.

4. Metody předepsané rozhraním com.sun.jdi.connect.Launchin­gConnector

Nyní si ukažme, jakým způsobem můžeme využít metodu com.sun.jdi.VirtualMachine­Manager.defaultConnector(). Z tabulky uvedené v předchozí kapitole již víme, že tato metoda vrátí konektor typu LaunchingConnector, což znamená konektor, který je schopen vytvořit nový virtuální stroj Javy a určit, která třída v nově vytvořeném JVM bude spuštěna (tato třída musí obsahovat statickou metodu main). LaunchingConnector je ve skutečnosti rozhraní rozšiřující jiné rozhraní nazvané jednoduše Connector:

com.sun.jdi.connect
public interface LaunchingConnector extends Connector

V rozhraní LaunchingConnector nalezneme předpis pro několik zajímavých metod, které jsou vypsány v tabulce níže:

# Metoda Popis
1 String name() vrátí řetězcový identifikátor konektoru
2 String description() vrátí řetězec s popisem konektoru v čitelné podobě
3 Transport transport() určeno pro zjištění transportního mechanismu použitého pro napojení na cílovou (laděnou) JVM
4 VirtualMachine launch(Map<String, ? extends Connector.Argument> arguments) spuštění cílové JVM s laděnou aplikací
5 Map<String,Connector.Argument> defaultArguments() vrátí argumenty použité konektorem společně s výchozími hodnotami těchto argumentů

Jak tento konektor spouští nový cílový virtuální stroj Javy se dozvíme až v následujících kapitolách, nyní nás bude zajímat především poslední metoda nazvaná defaultArguments(). Tato metoda vrátí mapu (asociativní pole), v níž jsou jako klíče použita jména argumentů a hodnoty jsou typu Connector.Argument. Důležité je, že tuto mapu není možné rozšiřovat, přesněji řečeno není možné rozšířenou mapu použít v metodě launch(). Můžeme ovšem měnit hodnoty jednotlivých argumentů, což je velmi důležité, protože v jednom argumentu je uložen i název třídy, která se má v nově vytvořeném virtuálním stroji Javy spustit.

5. První demonstrační příklad – výpis všech argumentů výchozího konektoru typu LaunchingConnector

V dnešním prvním demonstračním příkladu je ukázáno, jak lze využít metodu Bootstrap.virtualMachineManager() pro získání instance třídy implementující rozhraní VirtualMachineManager. Následně je zavolána metoda VirtualMachineManager.defau­ltConnector() vracející výchozí konektor dostupný v prakticky každé implementaci JRE. Tato metoda vrací instanci třídy implementující rozhraní LaunchingConnector. Jakmile jsme získali tento objekt, můžeme přes metodu Connector.defaultArguments() získat mapu obsahující jména i hodnoty všech argumentů výchozího konektoru. Tyto informace jsou následně vypsány na standardní výstup:

import java.util.Map;
 
import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.Connector.Argument;
 
/**
 * Trida, ktera vypise vsechny argumenty
 * vychoziho launching connectoru
 * (presneji receno konektoru typu LaunchingConnector).
 *
 * @author Pavel Tisnovsky
 */
public class JDIListLaunchingConnectorArguments {
 
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy implementujici rozhrani VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vychozi launching connector
        LaunchingConnector connector = virtualMachineManager.defaultConnector();
 
        // ziskat a vypsat vsechny parametry launching connectoru
        // (kazdy argument je typu com.sun.jdi.connect.Connector.Argument)
        Map<String, Argument> arguments = connector.defaultArguments();
        for (Map.Entry<String, Connector.Argument> argument : arguments.entrySet()) {
            System.out.println(argument.getKey() + "\t" + argument.getValue());
        }
    }
 
}

Překlad prvního demonstračního příkladu se provede následovně:

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

Spuštění zajistí příkaz:

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

(v obou případech je nutné udat cestu k javovskému archivu tools.jar)

Příklad výpisu výchozích parametrů po spuštění na Linuxu:

home    home=/usr/lib/jvm/java-6-openjdk/jre
options options=
main    main=
suspend suspend=true
quote   quote="
vmexec  vmexec=java

Příklad výpisu výchozích parametrů po spuštění na MS Windows:

home    home=C:\Program Files\Java\jdk1.6.0_01\jre
options options=
main    main=
suspend suspend=true
quote   quote="
vmexec  vmexec=java

6. Spuštění nového virtuálního stroje Javy s využitím výchozího konektoru typu LaunchingConnector

V následujících čtyřech kapitolách si vysvětlíme, jakým způsobem lze argumenty výchozího konektoru typu LaunchingConnector modifikovat takovým způsobem, abychom konektor „donutili“ ke spuštění virtuálního stroje Javy i se specifikovanou třídou obsahující metodu main. Začneme jednoduše – získáním instance třídy implementující rozhraní LaunchingConnector. To pro nás není nic nového, protože stejný kód byl využit i v prvním demonstračním příkladu. Jakmile kýžený objekt získáme, zavolá se uživatelská metoda nazvaná launchVirtualMachine(), které se předá jak získaný objekt s konektorem, tak i jméno třídy, která se má spustit v nově vytvářené JVM:

        // ziskat (jedinou) instanci tridy implementujici rozhrani VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vychozi launching connector
        LaunchingConnector connector = virtualMachineManager.defaultConnector();
 
        // spustit virtualni stroj Javy
        launchVirtualMachine(connector, "Test");

7. Specifikace třídy, jejíž metoda main() se má v nově vytvořeném virtuálním stroji spustit

V uživatelské metodě launchVirtualMachine() je nejprve nutné změnit výchozí argumenty konektoru takovým způsobem, aby se v nově vytvářeném virtuálním stroji Javy spouštěla vybraná třída. Tuto funkci zajistí následující kód:

        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("main").setValue(main);

Z předchozího textu víme, že do mapy vrácené metodou Connector.defaultArguments() sice není možné přidávat další prvky ani není možné prvky odebírat, můžeme však měnit hodnoty jednotlivých argumentů. Nás zajímá argument uložený pod klíčem „main“, jemuž s využitím metody com.sun.jdi.connect.Connec­tor.Argument.setValue() změníme výchozí hodnotu "" na jméno konkrétní třídy, která se má skutečně spustit.

Dále se cílový virtuální stroj Javy skutečně spustí, i když výchozím stavem JVM bude pozastavení – suspend). Spuštění může vypadat takto:

        try {
            System.out.println("Starting virtual machine");
            VirtualMachine virtualMachine = connector.launch(arguments);
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
        catch (VMStartException e) {
            e.printStackTrace();
        }
    }

Kompletní tělo uživatelské metody launchVirtualMachine() je vypsáno pod tímto odstavcem:

    /**
     * Spusteni noveho virtualniho stroje Javy.
     *
     * @param connector instance launching connectoru
     * @param main jmeno tridy, ktera se ma v nove JVM spustit
     */
    private static void launchVirtualMachine(LaunchingConnector connector, String main) {
        // zmena hodnoty argumentu se jmenem "main"
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("main").setValue(main);
 
        // spusteni virtualniho stroje Javy
        try {
            System.out.println("Starting virtual machine");
            VirtualMachine virtualMachine = connector.launch(arguments);
            // precteni standardniho vystupu spustene JVM
            processVMStandardOutput(virtualMachine);
            System.out.println("Virtual machine stopped");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
        catch (VMStartException e) {
            e.printStackTrace();
        }
    }

8. Přečtení standardního výstupu spuštěného virtuálního stroje Javy

Zbývají nám vyřešit dva problémy, které spolu úzce souvisí. První problém spočívá v tom, že je většinou nutné, aby debugger přečetl standardní a většinou i chybový výstup aplikace běžící v cílovém JVM. Pokud by k průběžnému čtení nedocházelo, byla by laděná aplikace běžící v cílovém JVM pozastavena ve chvíli, kdy by se naplnily vyrovnávací buffery standardního a chybového výstupu. Tento problém vyřešíme tak, že získáme objekt typu Process, který reprezentuje proces cílové JVM. Následně lze získat standardní/chybový výstup tohoto procesu s využitím metod Process.getInputStream() a Process.getErrorStrem(). Pojmenování těchto metod je poněkud matoucí, ale při bližším pohledu pochopitelné, protože standardní/chybový výstup jiného procesu je pro současný proces vstupním proudem, nikoli proudem výstupním. Standardní výstup získáme takto:

        Process process = virtualMachine.process();
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));

Jakmile je standardní výstup přesměrován, lze cílovou JVM znovu „rozjet“ metodou VirtualMachine.resume:

        // zmena stavu suspend->resume
        virtualMachine.resume();

Následně je nutné pečlivě přečíst celý standardní výstup cílové JVM. Celý kód zajišťující spuštění cílové JVM s přečtením jejího standardního výstupu vypadá následovně:

    /**
     * Precteni standardniho vystupu spustene JVM
     *
     * @param virtualMachine nove spustena JVM
     * @throws IOException
     */
    private static void processVMStandardOutput(VirtualMachine virtualMachine) throws IOException {
        Process process = virtualMachine.process();
        // nenechte se zmast - standardni vystup jineho procesu
        // je z naseho pohledu vstupem!
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
 
        // zmena stavu suspend->resume
        virtualMachine.resume();
 
        // precist standardni vystup nove JVM
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println("Child VM output: " + line);
        }
        reader.close();
    }

9. Druhý demonstrační příklad – spuštění nové JVM s přečtením výstupu testovací aplikace

Nyní si již můžeme ukázat kompletní zdrojový kód druhého demonstračního příkladu, který vytvoří novou instanci virtuálního stroje Javy a v tomto JVM spustí třídu nazvanou jednoduše „Main“. Veškerý text zapisovaný na standardní výstup cílové JVM je zachycen:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;
 
import com.sun.jdi.Bootstrap;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.VirtualMachineManager;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.connect.LaunchingConnector;
import com.sun.jdi.connect.VMStartException;
 
/**
 * Spusteni noveho virtualniho stroje Javy
 * a precteni jeho standardniho vystupu.
 *
 * @author Pavel Tisnovsky
 */
public class JDILaunchingConnectorTest {
 
    public static void main(String[] args) {
        // ziskat (jedinou) instanci tridy implementujici rozhrani VirtualMachineManager
        VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();
 
        // ziskat vychozi launching connector
        LaunchingConnector connector = virtualMachineManager.defaultConnector();
 
        // spustit virtualni stroj Javy
        launchVirtualMachine(connector, "Test");
    }
 
    /**
     * Spusteni noveho virtualniho stroje Javy.
     *
     * @param connector instance launching connectoru
     * @param main jmeno tridy, ktera se ma v nove JVM spustit
     */
    private static void launchVirtualMachine(LaunchingConnector connector, String main) {
        // zmena hodnoty argumentu se jmenem "main"
        Map<String, Connector.Argument> arguments = connector.defaultArguments();
        arguments.get("main").setValue(main);
 
        // spusteni virtualniho stroje Javy
        try {
            System.out.println("Starting virtual machine");
            VirtualMachine virtualMachine = connector.launch(arguments);
            // precteni standardniho vystupu spustene JVM
            processVMStandardOutput(virtualMachine);
            System.out.println("Virtual machine stopped");
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        catch (IllegalConnectorArgumentsException e) {
            e.printStackTrace();
        }
        catch (VMStartException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * Precteni standardniho vystupu spustene JVM
     *
     * @param virtualMachine nove spustena JVM
     * @throws IOException
     */
    private static void processVMStandardOutput(VirtualMachine virtualMachine) throws IOException {
        Process process = virtualMachine.process();
        // nenechte se zmast - standardni vystup jineho procesu
        // je z naseho pohledu vstupem!
        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
 
        // zmena stavu suspend->resume
        virtualMachine.resume();
 
        // precist standardni vystup nove JVM
        String line;
        while ((line = reader.readLine()) != null) {
            System.out.println("Child VM output: " + line);
        }
        reader.close();
    }
 
}

Samotná třída Test je velmi jednoduchá:

public class Test {
 
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println("Hello world!");
        }
    }
 
}

Překlad druhého demonstračního příkladu zajistí příkaz:

widgety

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

Spuštění se provede příkazem:

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

Po spuštění by se měl na standardní výstup vypsat následující text:

Starting virtual machine
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Child VM output: Hello world!
Virtual machine stopped

10. 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
Našli jste v článku chybu?
Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

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

Mordparta: trochu podchlazený 87. revír

Podnikatel.cz: Letáky? Lidi zuří, ale ony stále fungují

Letáky? Lidi zuří, ale ony stále fungují

Vitalia.cz: V Kauflandu už začaly Vánoce

V Kauflandu už začaly Vánoce

Vitalia.cz: Tesco nabízí desítky tun jídla zdarma

Tesco nabízí desítky tun jídla zdarma

Lupa.cz: Cimrman má hry na YouTube i vlastní doodle

Cimrman má hry na YouTube i vlastní doodle

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

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

Vitalia.cz: Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Fyzioterapeutka: Chůze naboso? Rozhodně ano!

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

Lupa.cz: Jak levné procesory změnily svět?

Jak levné procesory změnily svět?

Vitalia.cz: Jak Ondra o astma přišel

Jak Ondra o astma přišel

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

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

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

Wimbledon na Nova Sport až do 2019

Vitalia.cz: Pryč se zastaralým stravováním ve školách

Pryč se zastaralým stravováním ve školách

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

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

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

DigiZone.cz: Digi Slovakia zařazuje stanice SPI

Digi Slovakia zařazuje stanice SPI

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

DigiZone.cz: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB