javaseiten.de   |   Version 0.6
 

4.2. Struktur einer Klassendatei

Für unterschiedliche Plattformen gibt es auch unterschiedliche Dateiformate für compilierte ausführbare binäre Daten. So nutzt z.B. Windows das COFF-Dateiformat (Common Object File Format) und Linux das ELF-Dateiformat (Executable and Linking Format). Die Java-Plattform erreicht ihre binäre Kompatibilität zu anderen Plattformen durch ein universelles Dateiformat für Java-Programme, dem "Class File Format". Alle Dateien mit der Endung .class besitzen dieses Format (Klassendatei), d.h. die Abfolge der einzelnen Bytes innerhalb dieser Datei unterliegen einer fest vorgegebenen Struktur, die innerhalb der Java Virtual Machine Specification linkextern.gif definiert wird. Die einzelnen Bytes einer Klassendatei gehören zu bestimmten Blöcken. Diese Byte-Blöcke sind zum Teil in ihrerer Länge fest vorgegeben und zum anderen Teil variabel. Vor diesen variablen Byte-Bereichen steht oftmals ein separates Byte, das die Länge der nachfolgenden Bytes dieses Breiches angibt. Es ist deshalb oft notwendig die Bytes der Klassendatei nach und nach einzulesen und auszuwerten, um an einen bestimmten Block innerhalb der Datei zu gelangen. Im folgenden Abschnitt sollen konkret einzelne Byte-Gruppierungen anhand eines einfachen Java-Quellcodes aufgezeigt werden.

4.2.1. Überblick

Zunächst soll ausgehend von Listing 4.2 der Methodenbereich einer Klassendatei innerhalb eines Hexdumps dieser Klassendatei lokalisiert werden. Dazu wird als erstes das Java-Listing mit Hilfe eines Java-Compilers auf einen Bytecode abgebildet. Anschließend kann eine Disassemblierung der entstandenen Klassendatei mit Hilfe des JDK-Tools javap erfolgen. Die Bildschirmausgabe enthält die innerhalb der vorhanden Methoden vorkommenden Abkürzungen des JVM-Befehlssatzes (Mnemoniks) bei Verwendung der Option -c. Dieser Mnemonik ist ein Operationscode (Byte) fest zugeordnet. Die Bytes der Mnemoniks können dann innerhalb des Hexdumps von BytecodeExample.class lokalisiert werden. Der Bereich der Klassendatei, indem diese Bytes dann stehen wird als Methodenbereich bezeichnet. Die genannten Schritte sollen nun konkret nachvollzogen werden. Das Java-Ausgangslisting lautet wie folgt:

Listing 4.2. BytecodeExample.java. Beispielprogramm, dessen Java-Bytecode näher untersucht werden soll.

/* BytecodeExample.java */

public class BytecodeExample {

  public static void method1() {
    int j = 0;
    for (int i = 0; i < 10; i++) {
      j = j + 2;
    } 
    System.out.println(j);
  }

  public static void main(String[] args) {
    method1();
  }
}

Mit Hilfe des im JDK enthaltenen Compiler javac kann nun die Klassendatei BytecodeExample.class erzeugt werden:

> javac BytecodeExample.java

Der Java-Compiler javac übersetzt Java-Quelldateien (Java ist definiert durch die Java Language Specification linkextern.gif) in einen bytecodierten Befehlssatz bzw. in ein binäres Format, welches durch die JVM-Spezifikation vorgegeben ist. Alternativ könnte das Quellprogramm auch durch den schnellen Open-Source-Compiler Jikes linkextern.gif in Java-Bytecode übersetzt werden. Jikes ist auch für das Betriebssystem Windows als ausführbare Datei verfügbar (jikes.exe). Damit der Open Source Compiler verwendet werden kann, muss eine JRE (oder ein JDK) installiert sein, damit Jikes Zugang zu den Standardklassendatein hat. Eine Übersetzung des Beispielquellprogramms könnte dann unter Angabe des erforderlichen Klassenpfades z.B. wie folgt gestartet werden (Windows):

> jikes -classpath c:\programme\java\jre\lib\rt.jar BytecodeExample.java

Das Archiv rt.jar beinhaltet die sogenannten Bootstrap-Klassen (diese Laufzeitklassen enthalten die Kern-API der Java-Plattform).

 

Disassemblieren der Klassendatei mit javap

Mit Hilfe des Standard-JDK-Tools javap und der Option -c kann ein disassemblierter Code einer Klassendatei erhalten werden. Durch Eingabe von

> javap -c BytecodeExample

werden die folgenden Zeilen auf die Konsole ausgegeben:

Compiled from "BytecodeExample.java"
public class BytecodeExample extends java.lang.Object {
public BytecodeExample();
  Code:
    0: aload_0
    1: invokespecial #1; //Method java/lang/Object."<init>":()V
    4: return

public static void method1();
  Code:
    0: iconst_0
    1: istore_0
    2: iconst_0
    3: istore_1
    4: iload_1
    5: bipush 10
    7: if_icmpge 20
   10: iload_0
   11: iconst_2
   12: iadd
   13: istore_0
   14: iinc 1, 1
   17: goto 4
   20: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
   23: iload_0
   24: invokevirtual #3; //Method java/io/PrintStream.println:(I)V
   27: return

public static void main(java.lang.String[]);
  Code:
    0: invokestatic #4; //Method method1:()V
    3: return
}

Die Ausgabe zeigt, dass neben den in Listing 4.2 definierten zwei Methoden noch ein Default-Konstruktor durch den Compiler erzeugt wurde, der die Aufgabe hat den parameterlosen Konstruktor der Superklasse (Object) aufzurufen. Der Konstruktor wird durch drei JVM-Befehle implementiert. Der Opcode für aload_0 ist in hexadezimaler Schreibweise 2a. Für invokespecial und return sind die Opcodes b7 und b1. Auf die Mnemonik invokespecial folgen noch zwei Folgebytes und daher müsste die Bytefolge 2a b7 xx xx b1 im Hexdump der untersuchten Klassendatei auftauchen (xx steht für einen Platzhalter). Die Zuordnung der Mnemoniks in den beiden anderen Methoden method1 und main kann in gleicher Weise erfolgen. Im folgenden Hexdump der Klassendatei sind die Opcodes (mit eventuellen Folgebytes) der in den drei Methoden verwendeten Mnemoniks hervorgehoben dargestellt.

 

Hexadezimale Darstellung der Klassendatei

Durch Verwendung eines geeigneten Programms kann der folgende Hexdump von BytecodeExample.class erzeugt werden. Die Klassendatei wurde mit dem Java-Compiler javac erstellt (Java-Compiler anderer Hersteller oder Java-Compiler unterschiedlicher JDK-Versionen können zu modifizierten Bytecode-Darstellungen der Klassendatei führen).

Abbildung 4.3. Hexadezimale Darstellung der Klassendatei BytecodeExample.class.

ca fe ba be 00 00 00 31 00 1e 0a 00 06 00 10 09     .......1........     0
00 11 00 12 0a 00 13 00 14 0a 00 05 00 15 07 00     ................     16
16 07 00 17 01 00 06 3c 69 6e 69 74 3e 01 00 03     .......<init>...     32
28 29 56 01 00 04 43 6f 64 65 01 00 0f 4c 69 6e     ()V...Code...Lin     48
65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 07 6d     eNumberTable...m     64
65 74 68 6f 64 31 01 00 04 6d 61 69 6e 01 00 16     ethod1...main...     80
28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72     ([Ljava/lang/Str     96
69 6e 67 3b 29 56 01 00 0a 53 6f 75 72 63 65 46     ing;)V...SourceF     112
69 6c 65 01 00 14 42 79 74 65 63 6f 64 65 45 78     ile...BytecodeEx     128
61 6d 70 6c 65 2e 6a 61 76 61 0c 00 07 00 08 07     ample.java......     144
00 18 0c 00 19 00 1a 07 00 1b 0c 00 1c 00 1d 0c     ................     160
00 0b 00 08 01 00 0f 42 79 74 65 63 6f 64 65 45     .......BytecodeE     176
78 61 6d 70 6c 65 01 00 10 6a 61 76 61 2f 6c 61     xample...java/la     192
6e 67 2f 4f 62 6a 65 63 74 01 00 10 6a 61 76 61     ng/Object...java     208
2f 6c 61 6e 67 2f 53 79 73 74 65 6d 01 00 03 6f     /lang/System...o     224
75 74 01 00 15 4c 6a 61 76 61 2f 69 6f 2f 50 72     ut...Ljava/io/Pr     240
69 6e 74 53 74 72 65 61 6d 3b 01 00 13 6a 61 76     intStream;...jav     256
61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d     a/io/PrintStream     272
01 00 07 70 72 69 6e 74 6c 6e 01 00 04 28 49 29     ...println...(I)     288
56 00 21 00 05 00 06 00 00 00 00 00 03 00 01 00     V.!.............     304
07 00 08 00 01 00 09 00 00 00 1d 00 01 00 01 00     ................     320
00 00 05 2a b7 00 01 b1 00 00 00 01 00 0a 00 00     ...*............     336
00 06 00 01 00 00 00 03 00 09 00 0b 00 08 00 01     ................     352
00 09 00 00 00 48 00 02 00 02 00 00 00 1c 03 3b     .....H.........;     368
03 3c 1b 10 0a a2 00 0d 1a 05 60 3b 84 01 01 a7     .<........`;....     384
ff f3 b2 00 02 1a b6 00 03 b1 00 00 00 01 00 0a     ................     400
00 00 00 1a 00 06 00 00 00 06 00 02 00 07 00 0a     ................     416
00 08 00 0e 00 07 00 14 00 0a 00 1b 00 0b 00 09     ................     432
00 0c 00 0d 00 01 00 09 00 00 00 20 00 00 00 01     ........... ....     448
00 00 00 04 b8 00 04 b1 00 00 00 01 00 0a 00 00     ................     464
00 0a 00 02 00 00 00 0e 00 03 00 0f 00 01 00 0e     ................     480
00 00 00 02 00 0f                                   ......               496

In der mittleren Spalte der Hexdump-Darstellung steht ein druckbares ASCII-Zeichen, falls ein Byte innerhalb der Klassendatei im Bereich von hexadezimal 21 bis 7e liegt (ohne Steuerzeichen, alle ASCII-Zeichen sind aufwärtskompatibel zur Kodierung für Unicode Zeichen UTF-8). Außerhalb diese Bereichs wird ein Punkt "." ausgegeben. Würde z.B. das Byte mit dem hexadezimalen Wert 41 auftreten wäre innerhalb der zweiten Spalte an entsprechender Stelle ein "A" zu sehen. Die letzte Spalte gibt die Nummer des am linken Rand stehenden Bytes an, wobei die Zählung mit null beginnt. Die markierten Bytes im Hexdump gehören also zum Methodenbereich der Klassendatei. Der Methodenbereich setzt sich aber aus mehr als aus den genannten Bytes zusammen. Wie groß dieser Bereich ist soll im nächsten Abschnitt erläutert werden.

 

Bereiche der Klassendatei

Eine Klassendatei lässt sich grob in 10 Bereiche unterteilen. Die folgende Abbildung zeigt diese Unterteilung am Beispiel von BytecodeExample.class. Einige Bytes wurden notiert, die u.a. die einzelnen Bereiche noch einmal unterteilen.

Abbildung 4.4. Einteilung der Klassendatei in verschiedene Bereiche.

ca fe ba be .. .. .. .. 00 1e 0a .. .. .. .. 09     0
.. .. .. .. 0a .. .. .. .. 0a .. .. .. .. 07 ..     16
.. 07 .. .. 01 .. .. .. .. .. .. .. .. 01 .. ..     32
.. .. .. 01 .. .. .. .. .. .. 01 .. .. .. .. ..     48
.. .. .. .. .. .. .. .. .. .. .. .. 01 .. .. ..     64
.. .. .. .. .. .. 01 .. .. .. .. .. .. 01 .. ..     80
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     96
.. .. .. .. .. .. 01 .. .. .. .. .. .. .. .. ..     112
.. .. .. 01 .. .. .. .. .. .. .. .. .. .. .. ..     128
.. .. .. .. .. .. .. .. .. .. 0c .. .. .. .. 07     144
.. .. 0c .. .. .. .. 07 .. .. 0c .. .. .. .. 0c     160
.. .. .. .. 01 .. .. .. .. .. .. .. .. .. .. ..     176
.. .. .. .. .. .. 01 .. .. .. .. .. .. .. .. ..     192
.. .. .. .. .. .. .. .. .. 01 .. .. .. .. .. ..     208
.. .. .. .. .. .. .. .. .. .. .. .. 01 .. .. ..     224
.. .. 01 .. .. .. .. .. .. .. .. .. .. .. .. ..     240
.. .. .. .. .. .. .. .. .. .. 01 .. .. .. .. ..     256
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     272
01 .. .. .. .. .. .. .. .. .. 01 .. .. .. .. ..     288
.. .. .. .. .. .. .. .. .. .. .. 00 03 00 01 ..     304
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     320
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     336
.. .. .. .. .. .. .. .. 00 09 .. .. .. .. .. ..     352
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     368
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     384
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     400
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     416
.. .. .. .. .. .. .. .. .. .. .. .. .. .. 00 09     432
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     448
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     464
.. .. .. .. .. .. .. .. .. .. .. .. 00 01 .. ..     480
.. .. .. .. .. ..                                   496

Erläuterungen zu den einzelnen Bereichen der Klassendatei:

Tabelle 4.1. Einzelne Bereich einer Klassendatei.

Bereich I (Bytes 0-3) "Magic Bytes"
Eine Datei im "Class File Format" beginnt immer mit den vier Bytes ca fe ba be ("Magic Bytes"). Diese Bytefolge signalisiert, dass die nachfolgenden Bytes dem Format einer Klassendatei genügen.
Bereich II (Bytes 4-7) Version des Klassendatei-Formats
Eine Implementierung einer Java Virtual Machine kann in der Regel nur Klassendateien eines bestimmten Formats unterstützen. Die ersten beiden Bytes dieses Bereiches stehen für die sogenannte Minor- und das dritte und vierte Byte für die Major-Versionsnummer des Klassendatei-Formats. Die Version des Formats setzt sich aus diesen beiden Teilen zusammen: MajorVersionsnummer.MinorVersionsnummer (getrennt durch einen Punkt). Das Format der Beispiel-Klassendatei hat die dezimale Versionsnummer 49.0 .
Bereich III (Bytes 8-304) Konstantenpool
Direkt nach dem Major-Version-Byte beginnt der Konstantenpool, der aus mehreren einzelnen Konstanten besteht. Die Anzahl und Art der Konstanten wird während der Compilierung eines Quellprogramms festgelegt. Die Konstanten unterscheiden sich in ihrere Länge (Anzahl der Bytes, die diese Konstante repräsentieren). Der Konstantenpool ist daher ein Bereich innerhalb der Klassendatei, dessen Länge variabel ist. Um zum nächsten Byte-Block zu gelangen müsste zunächst der Inhalt des Konstantenpools ausgewertet werden (siehe auch Abschnitt 4.2.3).
Bereich IV (Bytes 305-306) Zugangscode der Klasse (access flags)
Zu einer .class-Datei gehört eine Klassendeklaration, die innerhalb eines Java-Quellprogramms steht. Innerhalb von Listing 4.2 wurde der Klassen-Modifikator public vor dem Schlüsselwort class und der Namensbezeichnung der Klasse verwendet. Dieser Modifikator hat u.a. zur Folge, dass die Bytes 305 und 306 in der zugehörigen Klassendatei die Werte 00 21 (hex.) besitzen. In der folgenden Anordnung stehen fünf Flags mit ihren zugehörigen Werten:
Flag-Name     Wert

ACC_PUBLIC    0x0001 
ACC_FINAL     0x0010 
ACC_SUPER     0x0020
ACC_INTERFACE 0x0200 
ACC_ABSTRACT  0x0400
ACC_PUBLIC, ACC_ABSTRACT und ACC_FINAL korrespondieren direkt mit den Klassen-Modifikatoren public, abstract und final. Das Flag ACC_SUPER wird aus Gründen der Abwärtskompatibiltät von aktuellen Java-Compilern gesetzt (Stichwort: Aufruf von Methoden durch den JVM-Befehl invokespecial). Dabei steht "SUPER" für Methoden der Superklasse der aktuellen Klasse. Der Wert 0x0021 (Flag-Maske) der entsprechenden Bytes im Beispiel ergibt sich aus den Werten von ACC_PUBLIC und ACC_SUPER:
00000000 00000001    0x0001
00000000 00100000    0x0020
-----------------
00000000 00100001    0x0021
Eine Klassendatei kann eine Klasse oder ein Interface definieren. Wird z.B. in Listing 4.2 keine Klasse, sondern ein Interface definiert (z.B. public interface BytecodeExample mit entsprechendem Quelltext), wird durch den Java-Compiler das Flag ACC_INTERFACE gesetzt und spiegelt sich in der resultierenden Flag-Maske wieder. Falls ACC_INTERFACE gesetzt ist, wird auch ACC_ABSTRACT gesetzt, da jedes Interface definitionsgemäß auch "implizit abstrakt" ist (ein Interface ist eine spezielle Form einer Klasse, die ausschließlich abstrakte Methoden beinhaltet).
Bereich V (Bytes 307-308) Name der Klasse (this class)
Im Beispiel haben die beiden Bytes die hexadezimalen Werte 00 05 und ergeben zusammen den Wert 0x0005. Der umgerechnet auch dezimale Wert 5 wird als Index im Konstantenpool der Klassendatei gewertet. Mit Hilfe der fünften Konstanten kann der Name BytecodeExample der Klasse erhalten werden, der innerhalb einer UTF8-Zeichenkette (22. Konstante) gespeichert ist.
Bereich VI (Bytes 309-310) Name der Superklasse (super class)
Ähnlich wie im beschriebenen Bereich V der Klassendatei, stehen die beiden Bytes dieses Bereichs für einen Index im Konstantenpool der Klassendatei. Im Beispiel ergibt sich aus den betrachteten Bytes 00 06 der Index 6 bzw. die 6. Konstante. Mit Hilfe dieser Konstanten und der 23. Konstanten, auf die ein Verweis erfolgt, wird die Klasse java/lang/Object als Superklasse der Beispielklasse identifiziert.
Bereich VII (Bytes 311-312) Anzahl und Name der Interfaces, die implemtiert bzw. erweitert werden
Grundsätzlich wird an dieser Stelle unterschieden, ob die vorliegende Klassendatei eine Klasse oder ein Interface definiert bzw. ob die Klasse ein oder mehrere Interfaces implementiert oder ob ein Interface ein oder mehrere Interfaces erweitert. Die beiden Bytes 00 00 im Beispiel geben an, dass die Beispielklasse BytecodeExample kein Interface implementiert. Falls eine Klasse ein oder mehrere Interfaces implementiert, schließt sich nach den beiden Bytes, die die Anzahl der implementierten Interfaces angegeben, ein Reihe weiterer Bytes an. Pro Interface folgen zwei Bytes, die einen Index im Konstantenpool codieren. Über diesen Index bzw. die Konstante kann der Name des implementierten Interfaces ermittelt werden. Ein Interface kann ein anderes Interface erweitern. Falls eine Klassendatei ein Interface definiert, erscheinen zunächst zwei Bytes, die die Anzahl der erweiterten Interfaces angeben (bei Klassen wird wie beschrieben an dieser Stelle die Anzahl der implementierten Interfaces angegeben). Die weiteren 2-Byte-Blöcke geben ebenfalls jeweils einen Index im Konstantenpool an, mit dem der Namen des erweiterten Interfaces erhalten werden kann.
Bereich VIII (Bytes 313-314) Anzahl und Beschreibung von Feldern (Instanzvariablen, Klassenvariablen)
Als Felder werden Instanzvariablen (wurden innerhalb einer Klasse definiert und werden bei jeder neu erzeugten Instanz der Klasse ebenfalls neu angelgt) und Klassenvariablen (statische Variablen, unabhängig von einer Instanz) bezeichnet. Die Beispielklasse BytecodeExample besitzt keine Felder und innerhalb der Klassendatei sind die beiden Bytes dieses Bereichs 00 00 (Anzahl der deklarierten Felder). Falls Felder vorhanden sind, folgen für jedes Feld eine Reihe von Bytes, die Informationen zu diesem Feld bereitstellen.
Bereich IX (Bytes 315-491) Methodenbereich
Der Methodenbereich beinhaltet den Java-Bytecode der einzelnen Methoden. In diesem Bereich werden die nötigen Informationen für jede Methode durch entsprechende Bytes bereitgestellt. Neben der Codierung von Methoden-Modifikatoren wie z.B. public, ist auch der Bytecode der benötigten JVM-Befehle wie z.B. iadd in diesem Bereich zu finden (siehe Abschnitt 4.2.2).
Bereich X (Bytes 492-501) Klassen-Attribute
An den Methodenbereich wird ein Attributbereich angehängt. Der Attributbereich der Klasse beinhaltet Informationen wie z.B. den Namen der zur Klassendatei gehörenden Quelldatei (SourceFile-Attribut) oder einen Eintrag darüber, ob die durch die Klassendatei definierte Klasse bzw. Interface als deprecated (veraltet) eingestuft wurde (Deprecated-Attribut). Der Attributbereich der Beispielklasse BytecodeExample beginnt mit den beiden Bytes 00 01. Die beiden Bytes geben an, dass der Attributbereich ein einziges Attribut enthält, das durch die folgenden Bytes codiert ist. Auf die Bytes 00 01 folgen die Bytes 00 0e 00 00 00 02 00 0f. Dabei steht 00 0e für einen Index im Konstantenpool und verweist auf die 14. Konstante der Beispielklasse. Die Konstante gibt an, dass es sich beim betrachteten Attribut um ein SourceFile-Attribut handelt. Die folgenden vier Bytes stehen für die Länge des Attributs bzw. die Anzahl der für dieses Attribut noch folgenden Bytes. Es folgen also im Beispiel noch die zwei Bytes 00 0f, die auf die 15. Konstante im Konstantenpool verweisen. Diese Konstante speichert die Zeichenkette "BytecodeExample.java" und damit den Namen der Java-Quelldatei. Die Klassen-Attribute schließen die Klassendatei ab.

Der bisherige Überblick hat die grobe Einteilung der Klassendatei BytecodeExample.class in 10 zusammenhängende Byteblöcke gezeigt. Die genannten Gruppierungen von Bytes sind allgemeingültig und sind daher in jeder Java-Klassendatei zu finden. Konstantenpool und Methodenbereich unterliegen wiederum festgelegten Strukturen, die in den folgenden Abschnitten näher untersucht werden sollen.

4.2.2. Methodenbereich

Die Klassendatei BytecodeExample.class besteht aus insgesamt 502 Bytes, wobei die Bytes 315-491 dem Methodenbereich zuzuordnen sind. Die folgende Darstellung zeigt den entsprechenden Byteabschnitt aus Abbildung 4.4:

.. .. .. .. .. .. .. .. .. .. .. 00 03 00 01 ..     304
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     320
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     336
.. .. .. .. .. .. .. .. 00 09 00 0b 00 08 00 01     352
00 09 00 00 00 48 00 02 00 02 00 00 00 1c 03 3b     368
03 3c 1b 10 0a a2 00 0d 1a 05 60 3b 84 01 01 a7     384
ff f3 b2 00 02 1a b6 00 03 b1 00 00 00 01 00 0a     400
00 00 00 1a 00 06 00 00 00 06 00 02 00 07 00 0a     416
00 08 00 0e 00 07 00 14 00 0a 00 1b 00 0b 00 09     432
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     448
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     464
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     480

Der Methodenbereich beginnt mit den beiden Bytes 00 03. Sie geben an, dass insgeamt drei Methoden im Methodenbereich codiert sind. Die erste Methode (Default-Konstruktor) beginnt mit Byte 317 und endet mit Byte 359. Die anschließenden 86 Bytes, beginnend mit 00 09 repräsentieren die Methode method1. Die main-Methode wird ab Byte 446 codiert und endet mit Byte 491, das gleichzeitig auch das Ende des Methodenbereichs darstellt.

Die Bedeutungen der 86 Bytes, die die Methode method1 codieren, sollen nun im einzelnen erläutert werden:

Tabelle 4.2. Codierung der Methode method1 innerhalb des Methodenbereichs.

00 09 Zugangscode (acces flags) der Methode, Bytes 360-361
Dei Methode method1 wurde in Listing 4.2 mit den Modifikatoren public und static deklariert. Der aus den beiden betrachteten Bytes gebildete Wert 0x0009 stellt eine Flag-Maske dar. Die Flag-Maske kann aus folgenden Werten zusammengesetzt sein:
Flag-Name         Wert

ACC_PUBLIC        0x0001 
ACC_PRIVATE       0x0002
ACC_PROTECTED     0x0004
ACC_STATIC        0x0008
ACC_FINAL         0x0010
ACC_SYNCHRONIZED  0x0020
ACC_NATIVE        0x0100
ACC_ABSTRACT      0x0400
ACC_STRICT        0x0800
Der Wert 0x0009 ergibt sich aus den Werten von ACC_PUBLIC und ACC_STATIC:
00000000 00000001    0x0001
00000000 00001000    0x0008
-----------------
00000000 00001001    0x0009
ACC_PUBLIC und ACC_STATIC korrespondieren direkt mit den im Quelltext verwendeten Methoden-Modifikatoren public und static.
00 0b Index im Konstantenpool: Name der Methode, Bytes 362-363
Die beiden Bytes haben den dezimalen Wert 11. Der Wert bezieht sich auf die 11. Konstante im Konstantenpool der Klassendatei BytecodeExample.class. Die 11. Konstante speichert die Zeichenkette "method1" und damit die Namensbezeichnung der betrachteten Methode.
00 08 Index im Konstantenpool: Methodendeskriptor, Bytes 364-365
Die beiden Bytes haben den dezimalen Wert 8. Der Wert bezieht sich auf die 8. Konstante im Konstantenpool der Klassendatei BytecodeExample.class. Die 8. Konstante speichert die Zeichenkette "()V", die einen Methodendeskriptor darstellt. Das leere Klammerpaar "()" verdeutlicht, dass die Methode method1 keine Paramter besitzt. Der Buchstabe "V" besagt, dass method1 keinen Wert zurückgibt.
00 01 Anzahl der folgenden Attribute, Bytes 366-367
Die beiden Bytes geben an, dass ein einziges Attribut folgt. Nach den beiden Bytes könnten mehrere bestimmte Attribute folgen. An dieser Stelle können sich die Methoden-Attribute Code, Exceptions und Deprecated anschließen. Jedes dieser Attribute stellt wiederum einen Byte-Block dar, der ebenfalls jeweils einer eigenen Struktur unterliegt. Welches Attribut nachfolgt zeigen die folgenden beiden Bytes.
00 09 Index im Konstantenpool: Name des Attributs, Bytes 368-369
Die beiden Bytes stehen wiederum für einen Index im Konstantenpool der betrachteten Klassendatei. Die indizierte 9. Konstante ist eine vom Typ "Utf8" und speichert die Zeichenkette "Code". Die nachfolgenden Bytes repräsentieren also ein Code-Attribut.
00 00 00 48 Anzahl der nachfolgenden Attribut-Bytes, Bytes 370-373
Diese vier Bytes geben die Länge des Attributs an, also die auf diesen 4-Byte-Block folgenden Bytes, die noch zu diesem Code-Attribut gehören.
00 02 Maximale Größe des Operandenstapels, Bytes 374-375
Diese Zahlenwerte geben an, dass sich maximal 2 Elemente auf dem Opeandenstapel befinden. Dabei ist derjenige Operandenstapel gemeint, der mit diesem Code-Attribut korrespondiert. Bei der Abarbeitung derjenigen Methode, die durch dieses Code-Attribut beschrieben wird, werden also maximal 2 Elemente auf dem Operandenstapel benötigt.
00 02 Anzahl der Elemente im Array der lokalen Variablen, Bytes 376-377
Die beiden Bytes 00 02 geben an, dass das Array der lokalen Variablen maximal 2 Elemente besitzt. Da es sich bei der durch dieses Code-Attribut beschriebenen Methode method1 aus Listing 4.2 um eine statische Methode ohne Parameter handelt, entsprechen den beiden Array-Elementen die int-Variablen i und j.
00 00 00 1c Anzahl der nachfolgenden Code-Bytes, Bytes 378-381
Die vier Bytes stehen für den dezimalen Wert 28 und geben die Größe des nachfolgenden Byte-Blocks an, der die Codierung der durch dieses Code-Attribut beschriebenen Methode beinhaltet (JVM-Befehle bestehend aus Operationscode und Operandenbytes).
03 3b 03 3c 1b 10 0a a2 00 0d 1a 05 60 3b 84 01 01 a7 ff f3 b2 00 02 1a b6 00 03 b1 Code-Bytes, Bytes 382-409
Dieser Byte-Block besteht aus 28 Code-Bytes der virtuellen Java-Maschine, der die betrachtete Methode method1 aus Listing 4.2 implementiert. Die einzelnen Bytes beschreiben die durch diese Methode verwendeten JVM-Befehle, welche sich aus einem Operationscode und eventuell folgenden Operandenbytes zusammensetzen. Den einzelnen Bytes können die folgenden mnemonischen Kürzel zugeordnet werden, welche auch bei der Disassemblierung der Klassendatei BytecodeExample.class erhalten wurden:
03              iconst_0
3b              istore_0
03              iconst_0
3c              istore_1
                  Beginn: for-Schleife 
1b              iload_1
10 0a           bipush 10
a2 00 0d        if_icmpge 20
1a              iload_0
05              iconst_2
60              iadd
3b              istore_0
84 01 01        iinc 1, 1
a7 ff f3        goto 4
                  Ende: for-Schleife 
b2 00 02        getstatic #2 
1a              iload_0
b6 00 03        invokevirtual #3
b1              return
00 00 Anzahl der Einträge in der Exception-Tabelle, Bytes 410-411
Wird innherhalb einer Methode, zur Behandlung von Ausnahmen, eine try-catch-Anweisung verwendet, erfolgt die Bytecodierung der Anweisung innerhalb der Klassendatei an dieser Stelle. Die Methode method1 enthält keine try-catch-Anweisung, was mit den beiden Bytes 00 00 deutlich wird.
00 01 Anzahl der Attribute innerhalb des Code-Attributs, Bytes 412-413
Aktuell befinden wir uns an dieser Stelle innerhalb eines Code-Attributs. Ein Code-Attribut kann wiederum bestimmte Attribute wie z.B. ein LineNumberTable-Attribut oder ein LocalVariableTable-Attribut enthalten. Beide Attribute speichern Debugging-Informationen.
00 0a Index im Konstantenpool: Name des Attributs, Bytes 414-415
00 0a bzw. dezimal 10 ist ein Index im Konstantenpool der betrachteten Klassendatei. Die 10. Konstante ist vom Typ "Utf8" und speichert die Zeichenkette "LineNumberTable" - die Namensbezeichnung des Attributs.
00 00 00 1a Anzahl der nachfolgenden Attribut-Bytes , Bytes 416-419
Dem LineNumberTable-Attribut sind noch die nachfolgenden 26 (0x1a) Bytes zugehörig.
00 06 Anzahl der Einträge in der Line-Number-Tabelle, Bytes 420-421
Die beiden Bytes 00 06 geben an, dass die Line-Number-Tabelle insgesamt 6 Einträge besitzt.
00 00 00 06 00 02 00 07 00 0a 00 08 00 0e 00 07 00 14 00 0a 00 1b 00 0b Line-Number-Tabelle, Bytes 422-445
Jeder Eintrag in der Tabelle besteht aus insgesamt vier Bytes. Das erste und zweite Byte jeden Eintrags stehen für eine Programmstelle innerhalb des Code-Byte-Blocks (Codierung der JVM-Befehle der Methode). Das dritte und vierte Byte geben die Zeilennummer im Quelltext des Java-Programms an, die mit der Bytecodestelle des JVM-Befehls innerhalb der Klassendatei korrespondiert. Dadurch wird eine mögliche Fehlersuche vereinfacht. Die Einträge in der Tabelle lauten dabei wie folgt:
Program Counter     Zeilennummer
  00 00               00 06
  00 02               00 07
  00 0a               00 08
  00 0e               00 07
  00 14               00 0a
  00 1b               00 0b
Die beiden ersten Einträge der Line-Number-Tabelle sollen kurz erläutert werden. Der erste Eintrag beginnt mit dem PC 00 00 oder kurz 0 und zeigt konkret auf das Byte 382 (Bytecodestelle 0 innerhalb des Code-Byte-Blocks), also auf den Operationcode 03 für dem JVM-Befehl iconst_0. Dieser Operationscode wird durch die Zeile 6 im Java-Quelltext aus Listing 4.2 verursacht. Die 6. Zeile lautet konkret:
int j = 0;
Der PC des zweiten Eintrags zeigt auf die Bytecodestelle 2 des Code-Byte-Blocks. An dieser Stelle steht das Byte 03. Das Byte steht wiederum für den JVM-Befehl iconst_0. Die Zeilennummer des 2. Eintrags ist 7 und verweist auf die entsprechende Zeile im Java-Quelltext:
for (int i = 0; i < 10; i++) {
Der JVM-Befehl iconst_0 entsteht durch die Anweisung int i = 0; innerhalb der 7. Quelltextzeile. Die Line-Number-Tabelle schließt die Bytecodierung der Methode method1 ab.

 

Den Code-Bytes (Bytes 382-409 innerhalb der Klassendatei), die die Methode method1 aus Listing 4.2) realsieren, werden mnemonische Kürzel zugeordnet. Die Abarbeitung dieser JVM-Befehle soll im einzelnen näher erläutert werden:

  • 0: iconst_0: Lege die Konstante mit dem Wert 0 und vom Typ int auf den Operandenstapel.

  • 1: istore_0: Speichere ein int in eine lokale Variable. Die im Befehl verwendete 0 ist ein Index im Array der lokalen Variablen. Das Array befindet sich in dem Frame, der für die Methode bereitgestellt wurde. Der zu speichernde Wert wird vom Operandenstapel (oberster Wert) geholt. Dieser Befehl und der vorhergende stehen für j=0.

  • 2: iconst_0: Lege die Konstante mit dem Wert 0 und vom Typ int auf den Operandenstapel.

  • 3: istore_1: Speichere ein weiters int in eine lokale Variable. Die im Befehl verwendete 1 ist ein Index im Array der lokalen Variablen. Dieser Befehl und der vorhergende stehen für i=0. Das betrachtete Array hat nun zwei Elemente (zwei lokale Variablen):

                                     int j   int i
    Array der lokalen Variablen:   |   0   |   0   |
  • 4: iload_1: Lade ein int von einer lokalen Variablen. Dabei wird der Wert mit Index 1 aus dem Array für lokale Variablen gelesen und auf dem Operandenstapel abgelegt.

  • 5: bipush 10: Lege den int-Wert 10 (dez.) auf den Stapel. Der Operandenstapel hätte an dieser Stelle zwei Einträge:

    Operandenstapel:   |  10  |
                       |   0  |
                       --------
  • 7: if_icmpge 20: Verzweige, falls der int-Vergleich "erfolgreich" ist. Die beiden obersten Wert, die sich aktuell auf dem Operandenstapel befinden, werden herunter genommen und verglichen. Der Befehl ist erfolgreich, wenn 0 >= 10 ist. Das ist aber nicht der Fall und die Ausführung des Programms geht mit dem Befehl, der auf diese Anweisung folgt weiter. Dem Befehl entspricht im zugehörigen Listing der Vergleich i<10 innerhalb der for-Schleife. Wäre der Vergleich nicht mehr erfüllt (z.B. nach mehrmaligem Durchlaufen der Schleife), würde eine Verzweigung zu Byte 20 der betrachteten Methode erfolgen (i hat zum aktuellen Zeitpunkt den Wert 0 und dieser steht an unterster Stelle des Stapels). Nach dem Opcode für diesen Befehl folgen zwei Bytes (Verzweigungsbytes), aus denen ein Offset ermittelt wird. Das Programm wird dann gegebenenfalls an diesem Offset bezüglich der aktuellen Adresse des Opcodes dieses Befehls fortgeführt.

  • 10: iload_0: Lege den Wert im Array bei Index 0 (entspricht dem Wert der lokalen Variablen j) auf dem Operandenstapel ab. Es wird eine 0 auf den Stapel gelegt, da für j nach der Initialisierung mit 0 noch keine Addition mit 2 durchgeführt wurde.

  • 11: iconst_2: Lege die int-Konstante mit dem Wert 2 auf den Operandenstapel:

    Operandenstapel:   |   2  |
                       |   0  |
                       --------
  • 12: iadd: Führe eine Addition zweier int-Werte aus. Die beiden obersten Wert des Operandenstapels werden herunter genommen und anschließend addiert. Das Ergebnis dieser Operation wird anschließend wieder auf den Stapel abgelegt (0+2=2):

    Operandenstapel:   |   2  |
                       --------
  • 13: istore_0: Der oberste Wert des Operandenstapels wird herunter genommen und in das Array für lokale Variablen bei Index 0 geschrieben (die lokale Variable j hat nun den Wert 2):

                                     int j   int i
    Array der lokalen Variablen:   |   2   |   0   |
  • 14: iinc 1,1: Inkrementiere die lokale Variable mit Index 1 innerhalb des Arrays (1 vor dem Komma) mit der Konstanten 1 (1 nach dem Komma). Dieser Befehl entpricht im zugehörigen Listing der Anweisung i++.

                                     int j   int i
    Array der lokalen Variablen:   |   2   |   1   |
  • 17: goto 4: Verzweige zu der angegebenen Adresse im Bytecode der aktuellen Methode. Der Bytecode diese Sprungbefehls enthält wieder zwei Verzweigungsbytes, aus denen ein Verzweigungsoffset berechnet wird. Der Befehl bezeichnet das Ende der for-Schleife.

  • 20: getstatic #2: Der Befehl liest ein statisches Feld einer Klasse aus. Mit "#2" wird eine Konstante innerhalb des Konstantenpools von BytecodeExample.class adressiert (siehe dazu Abschnitt 4.2.3). Die Konstante speichert die Informationen zum Feld: Statisches Feld out der Klasse System, wobei out vom Typ PrintStream ist. Im Quelltext aus Listing 4.2 wäre die entsprechende Stelle System.out direkt nach der for-Schleife.

  • 23: iload_0: Lade ein int von einer lokalen Variablen. Dabei wird der Wert mit Index 0 aus dem Array für lokale Variablen gelesen und auf dem Operandenstapel abgelegt. Der Index 0 entspricht dabei der im Java-Quelltext verwendeten Variablen j.

  • 24: invokevirtual #3: Der Befehl ruft eine Methode auf. Mit "#3" wird eine Konstante innerhalb des Konstantenpools von BytecodeExample.class adressiert (siehe dazu Abschnitt 4.2.3). Die Konstante speichert die Informationen zur Methode: Methode println der Klasse PrintStream. Im Quelltext aus Listing 4.2 wäre die entsprechende Stelle System.out.println(j);.

  • 27: return: Die betrachtete Methode method1 wird beendet bzw. wieder verlassen.

Der Methodenbereich unseres Beispiels wurde erläutert, wobei die Bedeutung der Bytes von method1 beschrieben wurde. Durch den JVM-Befehl invokevirtual #3 wurde bereits auf den Konstantenpool von BytecodeExample.class zugegriffen. Der Konstantenpool soll nun im nächsten Abschnitt genauer betrachtet werden.

4.2.3. Konstantenpool

Der Konstantenpool der Beispiel-Klassendatei BytecodeExample.class (siehe auch Listing 4.2) beginnt mit Byte 8 und endet mit Byte 304. Die folgende Darstellung zeigt den entsprechenden Byteabschnitt aus Abbildung 4.4:

.. .. .. .. .. .. .. .. 00 1e 0a .. .. .. .. 09     0
.. .. .. .. 0a .. .. .. .. 0a .. .. .. .. 07 ..     16
.. 07 .. .. 01 .. .. .. .. .. .. .. .. 01 .. ..     32
.. .. .. 01 .. .. .. .. .. .. 01 .. .. .. .. ..     48
.. .. .. .. .. .. .. .. .. .. .. .. 01 .. .. ..     64
.. .. .. .. .. .. 01 .. .. .. .. .. .. 01 .. ..     80
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     96
.. .. .. .. .. .. 01 .. .. .. .. .. .. .. .. ..     112
.. .. .. 01 .. .. .. .. .. .. .. .. .. .. .. ..     128
.. .. .. .. .. .. .. .. .. .. 0c .. .. .. .. 07     144
.. .. 0c .. .. .. .. 07 .. .. 0c .. .. .. .. 0c     160
.. .. .. .. 01 .. .. .. .. .. .. .. .. .. .. ..     176
.. .. .. .. .. .. 01 .. .. .. .. .. .. .. .. ..     192
.. .. .. .. .. .. .. .. .. 01 .. .. .. .. .. ..     208
.. .. .. .. .. .. .. .. .. .. .. .. 01 .. .. ..     224
.. .. 01 .. .. .. .. .. .. .. .. .. .. .. .. ..     240
.. .. .. .. .. .. .. .. .. .. 01 .. .. .. .. ..     256
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     272
01 .. .. .. .. .. .. .. .. .. 01 .. .. .. .. ..     288
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..     304

Aus den ersten beiden Bytes des Konstantenpools (0x001e = 30) kann die Anzahl der nachfolgenden Konstanten ermittelt werden:

Anzahl der Konstanten im Pool  =  30 - 1  =  29

Nach diesen beiden Bytes beginnt die erste Konstante mit dem Byte 0a. Eine Konstante innerhalb des Konstantenpools wird durch mehrere Bytes beschrieben. Dabei muss jede Konstante mit einem sogenannten Marker-Byte (tag byte) beginnen, das den Typ der Konstanten angibt. Die folgenden Konstantentypen werden im Konstantenpool verwendet:

Tabelle 4.3. Unterschiedliche Typen von Konstanten im Konstantenpool.

Typ der Konstanten Marker-Byte (hex.) Folgebytes
CONSTANT_Class 07 .. .. 2
CONSTANT_Fieldref 09 .. .. .. .. 4
CONSTANT_Methodref 0a .. .. .. .. 4
CONSTANT_InterfaceMethodref 0b .. .. .. .. 4
CONSTANT_String 08 .. .. 2
CONSTANT_Integer 03 .. .. .. .. 4
CONSTANT_Float 04 .. .. .. .. 4
CONSTANT_Long 05 .. .. .. .. .. .. .. .. 8
CONSTANT_Double 06 .. .. .. .. .. .. .. .. 8
CONSTANT_NameAndType 0c .. .. .. .. 4
CONSTANT_Utf8 01 .. .. *** 2 + x

Zwei Punkte in der mittleren Spalte stehen für ein Byte, das dem Marker-Byte folgt. Eine Konstante vom Typ Utf8 speichert eine Zeichenkette und die Länge der Konstanten ist daher abhängig von der Anzahl der Bytes, die für die Codierung der Zeichen erforderlich ist (wird in der Übersicht gekennzeichnet durch "***" bzw. "x"). Die einzelnen Bytes der ersten Konstanten sollen zunächst näher erläutert werden.

 

Auflösen der 1. Konstanten (Methodenreferenz)

Die erste Konstante ist ein Methodenverweis und wird durch die folgenden fünf Bytes repräsentiert (1. Zeile):

.. .. .. .. .. .. .. .. .. .. 0a 00 06 00 10 ..     0

1. Konstante: 0a 00 06 00 10
              0a  -->  Methodenreferenz
                 00 06  -->  Klassen-Index (dez. 6)
                       00 10  -->  Name/Typ-Index (dez. 16)
                       
1. Konstante (Methodref): Methode von Klasse #6, Name/Typ #16

Die zweite Zeile im Anschluss ist sozusagen die Entschlüsselung der fünf Bytes der 1. Konstanten. Sie besagt, dass die Methode zu derjenigen Klasse gehört, deren Namen durch die durch die 6. Konstante des Pools erhalten werden kann. Der Name und der Typ (Paramter und Rückgabewert) der Methode werden durch die 16. Konstante im Pool beschrieben. Werden die beiden zuletzt genannten Konstanten herangezogen, um die 1. Konstante weiter aufzulösen, können die nachfolgenden Informationen gewonnen werden (siehe auch weitere Konstanten im Konstantenpool):

1. Konstante  -->  Methode von Klasse Object
                   Name der Methode: <init>
                   Methodendeskriptor: ()V

Die Zeichenfolge <init> bezeichnet den Standardnamen für Konstruktoren und wird vom Compiler geliefert. Ein Konstruktor wird auch als Instanz-Initialisierungsmethode bezeichnet und kann nur innerhalb der Java Virtual Machine mit der JVM-Anweisung invokespecial aufgerufen werden. Der betrachtete Konstruktor (spezielle Methode) besitzt keine Parameter und hat keinen Rückgabewert. Dies wird durch den sogenannten Methodendeskriptor "()V" deutlich.

Methodendeskriptor. Ein Methodendeskriptor (Deskriptor: Kennwort, Schlüsselwort) ist ein String, der Informationen über die Paramter und den Rückgabewert einer Methode enthält. Er setzt sich aus dem Paramterdeskriptor (ParamterDescriptor) und dem Rückgabewertdeskriptor (ReturnDescriptor) wie folgt zusammen:

(ParameterDescriptor*)ReturnDescriptor

Der Parameterdeskriptor hat im Beispiel keinen Wert und daher verbleibt nur das Klammerpaar "()". Die Methode hat also keine Parameter. Für ReturnDescriptor steht "V". Durch den Buchstaben "V" wird angegeben, dass die Methode keinen Wert zurückgibt ("V" steht für void). Das Sternsymbol "*" gibt an, dass ParamterDescriptor keinmal oder mehrmals auftreten kann (falls mehrere Parameterdeskriptoren vorhanden sind werden sie ohne Leerstelle aneinandergehängt). endmarker.gif

Die 1. Konstante steht also für den Konstruktor public Object() der Klasse Object.

1. Konstante  -->  Konstruktor: public Object()
                   Klasse: java.lang.Object

Die Superklasse von BytecodeExample ist Object. Da in Listing 4.2 kein Konstruktor definiert wurde, wird vom Compiler automatisch ein parameterloser Default-Konstruktor erzeugt und in die Klassendatei aufgenommen (siehe auch Disassemblierung der Beispiel-Klassendatei mit javap). Der Default-Konstruktor hat die Aufgabe den parameterlosen Konstruktor der Superklasse aufzurufen, im Beispiel also public Object().

 

Auflösen der 2. Konstanten (Feldreferenz)

Nachdem die 1. Konstante im Konstantenpool aufgelöst wurde, soll nun die 2. Konstante, die einen Verweis auf ein Feld beinhaltet, näher untersucht werden (die Variablen einer Klasse werden als Felder (engl. Fields) dieser Klasse bezeichnet):

.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. 09     0
00 11 00 12 .. .. .. .. .. .. .. .. .. .. .. ..     16

2. Konstante: 09 00 11 00 12
              09  -->  Feldreferenz
                 00 11  -->  Klassen-Index (dez. 17)
                       00 12  -->  Name/Typ-Index (dez. 18)

2. Konstante (Fieldref): Feld von Klasse #17, Name/Typ #18

2. Konstante  -->  Feld von Klasse System
                   Name des Feldes: out 
                   Felddeskriptor: Ljava/io/PrintStream;
                   
2. Konstante  -->  Feld: static PrintStream out
                   Klasse: java.lang.System

Die 2. Konstante im Konstantenpool der betrachteten Klassendatei verweist also auf das Feld out, das sich innerhalb der Klasse System befindet.

Abbildung 4.5. Ausschnitt aus der API-Dokumentation der Klasse java.lang.System. Die 2. Konstante im Konstantenpool der Beispiel-Klassendatei verweist auf das Feld out dieser Klasse.

fieldsystemout.jpg

Das Feld ist vom Typ PrintStream und es können damit die Methoden der Klasse java.io.PrintStream wie z.B. println(int x) genutzt werden. Die Information über den Typ des Feldes wird durch den Felddeskriptor Ljava/io/PrintStream; geliefert. Der Großbuchstabe L am Anfang des Deskriptors gibt an, dass der Typ des Feldes ein Referenztyp (Referenz auf ein Objekt) ist.

Felddeskriptor. Ein Felddeskriptor ist ebenfalls ein Zeichenkette, die den Typ des Feldes beschreibt. Neben Referenztypen werden auch weitere Buchstaben verwendet, falls der Feldtyp ein primitiver Typ ist. Für ein Feld vom Typ int ist der zugehörige Felddeskriptor der Buchstabe I. Die folgende Übersicht zeigt die Bedeutung der einzelnen Schlüsselzeichen, die innerhalb eines Felddeskriptors auftreten:

Tabelle 4.4. Schlüsselzeichen innerhalb eines Felddeskriptors.

Schlüsselzeichen Typ
B byte
S short
I int
J long
F float
D double
C char
Z boolean
L<classname> Referenztyp (Referenz auf ein Objekt der Klasse <classname>)
[ Referenztyp (Referenz auf ein eindimensionales Array)

Die folgenden Beispiele zeigen einige Felder und die zugehörigen Felddeskriptoren, die als mögliche Konstanten (Zeichenketten) innerhalb eines Konstantenpools auftreten können:

Feld Felddeskriptor
int i I
boolean b Z
Object obj Ljava/lang/Object;
PrintStream out Ljava/io/PrintStream;
String[] str [Ljava/lang/String;
double[][] a [[D

An dieser Stelle soll noch eine Methode (drei Parameter und Rückgabewert) und ihr zugehöriger Methodendeskriptor genannt werden:

Methodendeklaration:  public Object method2(int i, boolean b, String[] sa) { ... }
Methodendeskriptor:  (IZ[Ljava/lang/String;)Ljava/lang/Object;

Der Methodendeskriptor enthält die drei Felddeskriptoren der einzelnen Parameter der Methode innerhalb des Klammerpaares. Der Felddeskriptor des Rückgabewertes Ljava/lang/Object; folgt unmittelbar im Anschluss. endmarker.gif

 

Konstante vom Typ Class

Innerhalb der 1. Konstanten

1. Konstante (Methodref): Methode von Klasse #6, Name/Typ #16

wird durch die Angabe des Index #6 auf die 6. Konstante im Konstantenpool verwiesen. Diese Konstante ist vom Typ Class und lautet:

.. 07 00 17 .. .. .. .. .. .. .. .. .. .. .. ..     32

6. Konstante: 07 00 17
              07  -->  Konstante vom Typ Class
                 00 17  -->  Index im Konstantenpool (dez. 23)
                             (Name der Klasse)
                       
6. Konstante (Class): Klasse #23

Eine Konstante vom Typ Class besteht nach Tabelle 4.3 aus insgesamt drei Bytes, wobei die beiden letzten Bytes einen Index im Konstantenpool festlegen.

 

Konstante vom Typ NameAndType

Die 1. Konstante verweist durch die Angabe #16 auf eine weitere Konstante, auf die 16. Konstante vom Typ NameAndType.

.. .. .. .. .. .. .. .. .. .. 0c 00 07 00 08 ..     144

16. Konstante: 0c 00 07 00 08
               0c  -->  Konstante vom Typ NameAndType
                  00 07  -->  Index im Konstantenpool
                              (Name der der Methode)
                        00 08  -->  Index im Konstantenpool
                                    (Methodendeskriptor)

16. Konstante (NameAndType): Name #7, Typ #8

Eine Konstante vom Typ NameAndType besteht aus fünf Bytes, wobei jeweils zwei Bytes einen Index im Konstantenpool codieren. Die durch diese Indizes adressierten Konstanten sind jeweils vom Typ Utf8.

 

Konstante vom Typ Utf8

Eine Konstante vom Typ Utf8 speichert eine Zeichenkette bestehend aus Unicode-Zeichen mit der Zeichencodierung UTF-8 (8-bit Unicode Transformation Format). Als Beispiel für ein Konstante vom Typ Utf8 soll die 8. Konstante dienen:

.. .. .. .. .. .. .. .. .. .. .. .. .. 01 00 03     32
28 29 56 .. .. .. .. .. .. .. .. .. .. .. .. ..     48

8. Konstante: 01 00 03 28 29 56
              01  -->  Konstante vom Typ Utf8
                 00 03  -->  Anzahl der nachfolgenden Bytes, die noch zur
                             Konstanten gehören
                       28  -->  Zahlencodierung für das Zeichen "("
                          29  -->  Zahlencodierung für das Zeichen ")"
                             56  -->  Zahlencodierung für das Zeichen "V"

8. Konstante (Utf8): ()V

Die 8. Konstante ist im Beispiel der Methodendeskriptor des durch den Compiler erzeugten Default-Konstruktors.

 

Konstante vom Typ String

In der zu Listing 4.2 gehörenden Klassendatei BytecodeExample.class ist keine Konstante vom Typ String enthalten. Eine Instanzvariable wie z.B.

String str = "Hallo";

innerhalb des Beispiel-Quelltextes hätte zur Folge, dass der Compiler bei der Übersetzung in die Klassendatei eine entrechende Konstante vom Typ String erzeugt. Nach Tabelle 4.3 beginnt eine Konstante diesen Typs mit 08 und besteht aus insgesamt drei Bytes. Die letzten beiden Bytes stehen für einen Index im Konstantenpool; die String-Konstante verweist also auf eine weitere Konstante im Konstantenpool, welche vom Typ Utf8 ist und den Inhalt der Zeichenkette, z.B. "Hallo", speichert.

 

Konstanten vom Typ Integer und Float

Konstanten vom Typ Integer und Float innerhalb des Konstantenpools von BytecodeExample.class könnten ebenfalls nachträglich eingefügt werden, wenn im Beispiel Listing 4.2 die beiden Instanzvariablen

int ivar = 32768;
float fvar = 1.23e12f;

zusätzlich eingetragen wären. Eine Initialisierung der Variablen ivar z.B. mit Werten zwischen 0 und 32767 hat jedoch zur Folge, dass keine Integer-Konstante im Pool erzeugt wird - der Zahlenwert wird in diesem Fall direkt innerhalb des Methodenbereichs stehen und benötigt lediglich zwei Bytes im Gegensatz zu einer Integer-Konstanten im Pool mit insgesamt fünf Bytes. Die Konstante beginnt nach Tabelle 4.3 mit 03 und die folgenden vier Bytes codieren den Zahlenwert 32768 durch die Bytes 00 00 80 00. Eine entsprechende Konstante vom Typ Integer würde also im Konstantenpool durch 03 00 00 80 00 repräsentiert. Eine Konstante vom Typ Float besteht ebenfalls aus insgesmat fünf Bytes. Für die verwendete Gleitkommazahl 1.23e12 besteht die Konstante im Pool aus der Byteabfolge 04 53 8f 30 db (siehe dazu auch Binärdarstellung einer Gleitkommazahl (32 Bit)).

 

Konstanten vom Typ Long und Double

Durch zusätzliches Einfügen der Instanzvariablen

long lvar = 123;
double dvar = 2.34e23d;

in den Quelltext aus Listing 4.2 werden durch den Java-Compiler zusätzliche Konstanten vom Typ Long und Double in den Konstantenpool von BytecodeExample.class aufgenommen. Im Gegensatz zu int-Variablen im Java-Quelltext wird bei long-Variablen immer eine entsprechende Konstante im Pool erzeugt, unabhängig vom Wert dieser Variablen. Konstanten vom Typ Long und Double werden jeweils durch 9 Bytes innerhalb des Konstantenpools codiert. Nach dem Marker-Byte der jeweiligen Konstanten (05 bzw. 06) folgen 8 Bytes zur Abspeicherung der Zahlenwerte:

05 00 00 00 00 00 00 00 7b
06 44 c8 c6 95 2c 6b 6e bf

Die erste Bytefolge codiert die Zahl 123 im Konstantenpool und die zweite Reihe steht für die Gleitkommazahl 2.34e23. Konstanten vom Typ Long und Double belegen zwei Einträge im Konstantenpool (Definition bzw. Festlegung der Java-Entwickler). Steht z.B. eine Konstante vom Typ Long an 2. Stelle im Pool wird die nächste Konstante mit 4 nummeriert. Die Festlegung, dass Konstanten vom Typ Long und Double zwei Einträge im Konstantenpool belegen, wird innerhalb der "Java Virtual Machine Specification, Second Edition" wie folgt kommentiert (Zitat): "In retrospect, making 8-byte constants take two constant pool entries was a poor choice."

 

Konstante vom Typ InterfaceMethodref

Eine Konstante vom Typ InterfaceMethodref besteht ebenfalls wie eine Konstante vom Typ Methodref aus insgesamt fünf Bytes. Nach dem Byte 0b, das den Typ InterfaceMethodref kennzeichnet, stehen die folgenden beiden Bytes für eine Index im Konstantenpool, also einen Verweis auf eine weitere Konstante im Pool, mit der der Name des Interfaces erhalten werden kann. Die letzten beiden Bytes der Konstatnten codieren ebenfalls einen Index im Konstantenpool. Durch die indizierte Konstante kann der Name der Interface-Methode und deren Methodendeskriptor ermittelt werden. Ein Java-Quelltext, durch den eine Konstante vom Typ InterfaceMethodref in der zugehörigen Klassendatei erzeugt wird, ist in der Befehlsbeschreibung zum JVM-Befehl invokeinterface enthalten.

 

Auflistung der Konstanten im Konstantenpool

Die bisher erarbeiteten Kenntnisse über den Aufbau eines Konstantenpools und die darin enthaltenen unterschiedlichen Typen von Konstanten können nun genutzt werden, um ein kurzes Beispielprogramm zu entwickeln, das die einzelnen Konstanten eines Pools aus einer Klassendatei ausliest und auf dem Bildschirm ausgibt. Vor dem Konstantenpool stehen immmer 8 Bytes und der zu analysierende Byte-Block beginnt damit mit Byte 8 (die Position der Bytes beginnt mit 0). Nach der Auswertung von Byte 8 und 9 ist die Anzahl der im Pool enthaltenen Konstanten ermittelt und mit Byte 10 beginnt die 1. Konstante. Mit dem erste Marker-Byte (tag byte) an Position 10 ist auch die Länge der 1. Konstanten bekannt, falls diese nicht den Wert 0x01 hat (Konstanten vom Typ Utf8 sind als einzige Konstanten von der Länge her variabel). Nach Auswertung der zur 1. Konstanten gehörenden Bytes folgt das nächste Marker-Byte, das den Beginn der 2. Konstanten im Bytestrom markiert. Da die Anzahl der Konstanten im Pool bereits ermittelt wurde, wird nun solange mit der Analyse fortgefahren bis die letzte Konstante ausgewertet und auf dem Bildschirm ausgegeben wurde. Ein Programm zur Analyse eines Konstantenpools könnte wie folgt formuliert werden:

Listing 4.3. AnalyzeConstantpool.java. Auslesen der Konstanten einer Klassendatei.

/* AnalyzeConstantpool.java */

import java.io.*;

public class AnalyzeConstantpool {
  
  public static void getConstants(FileInputStream stream, int length) {
    int i = 0;
    int data;
    int[] dump = new int[length];
    try {
      while ((data = stream.read()) != -1) {
        dump[i] = data;
        i++;    
      }
    } catch (IOException e) {
      System.out.println(e.toString());
    }
    
    boolean classFile = (dump[0] == 0xca) && (dump[1] == 0xfe) &&
                        (dump[2] == 0xba) && (dump[3] == 0xbe);
     
    if (!classFile) { 
      System.out.println("Keine class-Datei!");
      System.exit(1);
    }  
    
    // Anzahl der Konstanten im Pool
    int cpSize = ((dump[8] << 8) | dump[9]) - 1;
    System.out.println("Es sind " + cpSize + 
        " Konstanten im Konstantenpool enthalten.");
    
    // Konstanten
    int k = 10;  // 1. Konstante beginnt bei Byte 10
    for (int j = 1; j <= cpSize; j++) {
      switch (dump[k]) {
        // CONSTANT_Class, 3 Bytes
        case 0x07: System.out.println(j + ". Konstante (Class): " + 
                       "Klasse #" + ((dump[k+1] << 8) | dump[k+2]));
                   k = k + 3;
                   break;
        // CONSTANT_Fieldref, 5 Bytes
        case 0x09: System.out.println(j + ". Konstante (Fieldref): " +
                       "Feld von Klasse #" + ((dump[k+1] << 8) | dump[k+2]) + 
                       ", Name/Typ #" + ((dump[k+3] << 8) | dump[k+4]));
                   k = k + 5;
                   break; 
        // CONSTANT_Methodref, 5 Bytes
        case 0x0a: System.out.println(j + ". Konstante (Methodref): " +
                       "Methode von Klasse #" + ((dump[k+1] << 8) | dump[k+2]) + 
                       ", Name/Typ #" + ((dump[k+3] << 8) | dump[k+4]));
                   k = k + 5;
                   break;
        // CONSTANT_InterfaceMethodref, 5 Bytes
        case 0x0b: System.out.println(j + ". Konstante (InterfaceMethodref): " +
                       "Interface-Methode von Klasse #" + ((dump[k+1] << 8) | 
                       dump[k+2]) + ", Name/Typ #" + ((dump[k+3] << 8) | 
                       dump[k+4]));
                   k = k + 5;
                   break;
        // CONSTANT_String, 3 Bytes
        case 0x08: System.out.println(j + ". Konstante (String): " + 
                       "String #" + ((dump[k+1] << 8) | dump[k+2]));
                   k = k + 3;
                   break;
        // CONSTANT_Integer, 5 Bytes
        case 0x03: System.out.println(j + ". Konstante (Integer): " +
                       ((dump[k+1] << 24) | (dump[k+2] << 16) |
                       (dump[k+3] << 8) | dump[k+4]));
                   k = k + 5;
                   break;
        // CONSTANT_Float, 5 Bytes
        case 0x04: int bitsI = (dump[k+1] << 24) | (dump[k+2] << 16) |
                       (dump[k+3] << 8) | dump[k+4];
                   float f = Float.intBitsToFloat(bitsI);
                   System.out.println(j + ". Konstante (Float): " + f);
                   k = k + 5;
                   break;
        // CONSTANT_Long, 9 Bytes           
        case 0x05: long l = ((long)dump[k+1] << 56) | ((long)dump[k+2] << 48) | 
                            ((long)dump[k+3] << 40) | ((long)dump[k+4] << 32) |
                            ((long)dump[k+5] << 24) | ((long)dump[k+6] << 16) | 
                            ((long)dump[k+7] << 8) | (long)dump[k+8];
                   System.out.println(j + ". Konstante (Long): " + l);
                   k = k + 9;
                   j = j + 1;
                   System.out.println(j + 
                       ". Konstante: Konstanten vom Typ Long belegen " +
                       "2 Konstanten im Konstantenpool!");
                   break;
        // CONSTANT_Double, 9 Bytes
        case 0x06: long ld = ((long)dump[k+1] << 56) | ((long)dump[k+2] << 48) | 
                             ((long)dump[k+3] << 40) | ((long)dump[k+4] << 32) |
                             ((long)dump[k+5] << 24) | ((long)dump[k+6] << 16) | 
                             ((long)dump[k+7] << 8) | (long)dump[k+8];
                   double d = Double.longBitsToDouble(ld);
                   System.out.println(j + ". Konstante (Double): " + d);
                   k = k + 9;
                   j = j + 1;
                   System.out.println(j +
                       ". Konstante: Konstanten vom Typ Double belegen " +
                       "2 Konstanten im Konstantenpool!");
                   break;
        // CONSTANT_NameAndType, 5 Bytes
        case 0x0c: System.out.println(j + ". Konstante (NameAndType): " +
                       "Name #" + ((dump[k+1] << 8) | dump[k+2]) + 
                       ", Typ #" + ((dump[k+3] << 8) | dump[k+4]));
                   k = k + 5;
                   break;
        // CONSTANT_Utf8, 2 Bytes + utf8Length Bytes
        case 0x01: int utf8Length = (dump[k+1] << 8) | dump[k+2];
                   System.out.print(j + ". Konstante (Utf8): ");
                   for (int m = 1; m <= utf8Length; m++) {
                     System.out.print((char)dump[k+2+m]); 
                   }  
                   System.out.println();
                   k = k + 3 + utf8Length;
                   break;
      }             
    }  
  }  
  
  public static void main(String[] args) {
    if (args.length == 1) {
      String fileName = args[0];
      try {
        File f = new File(fileName);
        int l = (int)f.length();
        FileInputStream in = new FileInputStream(f);
        getConstants(in, l);
        in.close();
      } catch (Exception e) {
        System.out.print(e.toString());
      }  
    } else {
      System.out.println("usage: java AnalyzeConstantpool <filename>\n");
    }  
  }
}

Die Anweisung

> java AnalyzeConstantpool BytecodeExample.class

führt zur folgenden Ausgabe:

Es sind 29 Konstanten im Konstantenpool enthalten.
1. Konstante (Methodref): Methode von Klasse #6, Name/Typ #16
2. Konstante (Fieldref): Feld von Klasse #17, Name/Typ #18
3. Konstante (Methodref): Methode von Klasse #19, Name/Typ #20
4. Konstante (Methodref): Methode von Klasse #5, Name/Typ #21
5. Konstante (Class): Klasse #22
6. Konstante (Class): Klasse #23
7. Konstante (Utf8): <init>
8. Konstante (Utf8): ()V
9. Konstante (Utf8): Code
10. Konstante (Utf8): LineNumberTable
11. Konstante (Utf8): method1
12. Konstante (Utf8): main
13. Konstante (Utf8): ([Ljava/lang/String;)V
14. Konstante (Utf8): SourceFile
15. Konstante (Utf8): BytecodeExample.java
16. Konstante (NameAndType): Name #7, Typ #8
17. Konstante (Class): Klasse #24
18. Konstante (NameAndType): Name #25, Typ #26
19. Konstante (Class): Klasse #27
20. Konstante (NameAndType): Name #28, Typ #29
21. Konstante (NameAndType): Name #11, Typ #8
22. Konstante (Utf8): BytecodeExample
23. Konstante (Utf8): java/lang/Object
24. Konstante (Utf8): java/lang/System
25. Konstante (Utf8): out
26. Konstante (Utf8): Ljava/io/PrintStream;
27. Konstante (Utf8): java/io/PrintStream
28. Konstante (Utf8): println
29. Konstante (Utf8): (I)V

4.2.4. Feldbereich

Die Variablen einer Klasse werden als die Felder der Klasse bezeichnet (Klassenvariablen, Instanzvariablen). Ein minimaler Feldbereich einer Klassendatei besteht aus den beiden Bytes 00 00. Der Quelltext aus Listing 4.2 hat z.B. keine Klassen- und Instanzvariablen und daher besteht der Feldbereich der zugehörigen Klassendatei nur aus den beiden Bytes 00 00, die die Anzahl der Felder angeben. Werden z.B. die folgenden Felder innerhalb einer Klasse notiert

String str = "Hallo";
int ivar = 32768;
static double dvar = 2.34e23d;
private static final int jconst = 3;

dann besteht der zugehörige Feldbereich aus insgesamt 41 Bytes:

00 04
00 00 .. .. .. .. 00 00
00 00 .. .. .. .. 00 00
00 08 .. .. .. .. 00 00
00 1a .. .. .. .. 00 01 .. .. 00 00 00 02 .. ..

Die beiden Bytes 00 04 geben die Anzahl der Felder im Feldbereich an. Danach folgen die einelnen Byte-Blöcke (Zeilen), die die verwendeten vier Felder codieren. Jeder dieser Byte-Blöcke beginnt mit zwei Bytes (access flags), die Informationen über die Zugangserlaubnis und die Eigenschaften des Feldes liefern. Dabei stellen diese beiden Bytes eine Bit-Maske dar, die aus den folgendn Werten gebildet werden kann:

Flag-Name Wert
ACC_PUBLIC 0x0001
ACC_PRIVATE 0x0002
ACC_PROTECTED 0x0004
ACC_STATIC 0x0008
ACC_FINAL 0x0010
ACC_VOLATILE 0x0040
ACC_TRANSIENT 0x0080

Die Namensbezeichnungen der Flags wie z.B. ACC_PUBLIC oder ACC_FINAL korrespondieren direkt mit den Feld-Modifikatoren public bzw. final. Der Wert 0x001a (Flag-Maske) des vierten Feldes jconst ergibt sich aus den Werten von ACC_PRIVATE, ACC_STATIC und ACC_FINAL:

00000000 00000010    0x0002
00000000 00001000    0x0008
00000000 00010000    0x0010
-----------------
00000000 00011010    0x001a

Die vier Bytes nach diesen beiden Bytes (access flags) stehen für zwei Indizes im Konstantenpool der Klassendatei (durch Punkte gekennzeichnet). Die durch diese Indizes adressierten Konstanten im Pool speichern Informationen zum Feld (Name des Feldes, Felddeskriptor). Danach folgen zwei Bytes, die die Anzahl der folgenden Attribute für dieses Feld angeben. Der Byte-Block für das Feld jconst zeigt, dass dieses ein Attribut besitzt (die ersten drei Felder besitzen keine Attribute, was durch die abschließenden Bytes 00 00 deutlich wird):

00 1a .. .. .. .. 00 01 .. .. 00 00 00 02 .. ..

Das Feld-Attribut an sich wird repräsentiert durch:

.. .. 00 00 00 02 .. ..

Die ersten beiden Bytes des Attributs verweisen auf eine Konstante im Konstantenpool der Klassendatei, mit der der Name des Attributs erhalten werden kann. Dabei handelt es sich um ein ConstantValue-Attribut (siehe dazu Abschnitt 4.2.5). Die letzten beiden Bytes verweisen wiederum auf eine Konstante im Pool vom Typ Integer. Diese Konstante speichert den Wert 3. Die Bytefolge 00 00 00 02 ist in jedem ConstantValue-Attribut enthalten und steht für die nachfolgenden Bytes, die diesem Attribut angehören (resultiert aus der einheitlichen Byte-Strukturierung von Attributen).

4.2.5. Attribute

Attribute (Beifügungen) treten innerhalb unterschiedlicher Bereiche einer Klassendatei auf. Attribute können auch innerhalb eines anderen Attributs auftreten (Attribut eines Attributs). Die Bereiche, in denen Attribute zugelassen sind, lauten:

  • Klassenbereich (Attributabschnitt direkt im Anschluss an den Methodenbereich)

  • Feldbereich

  • Methodenbereich

  • Innerhalb eines Code-Attributs

Die folgende Übersicht nennt definierte Attribute und deren mögliche Verwendung in verschiedenen Abschnitten einer Klassendatei:

Tabelle 4.5. Attribute innerhalb einer Klassendatei.

Attribut Bereich Beschreibung
SourceFile Klassenbereich Verweis auf den Namen der Quelldatei, von dem die Klassendatei hervorgegangen ist. Die letzten Bytes 00 0e 00 00 00 02 00 0f der Beispiel-Klassendatei BytecodeExample.class stehen für ein SourceFile-Attribut.
Code Methodenbereich Innerhalb des Methodenbereichs einer Klassendatei steht für jede implementierte Methode ein Code-Attribut. BytecodeExample.class enthält insgesamt drei Code-Attribute (inklusiv einem Code-Attribut für den vom Compiler automatisch erzeugten Default-Konstruktor). Ein Code-Attribut enthält die einzelnen Bytes der JVM-Befehle. Innerhalb eines Code-Attributs können wiederum die Attriubte LineNumberTable und LocalVariableTable stehen. Siehe dazu Abschnitt 4.2.2.
LineNumberTable Innerhalb eines Code-Attributs. Ein Code-Attribut kann wiederum ein LineNumberTable-Attribut enthalten, das Debugging-Informationen enthält. BytecodeExample.class enthält ein LineNumberTable-Attribut (siehe Abschnitt 4.2.2).
LocalVariableTable Innerhalb eines Code-Attributs. Ein LocalVariableTable-Attribut kann ebenfalls ein Bestandteil eines Code-Attributs sein. Mit Hilfe eines LocalVariableTable-Attributs kann ein Debugger den Wert einer lokalen Variablen während der Abarbeitung der einzelnen JVM-Befehle ermitteln. LocalVariableTable-Attribute innerhalb einer Klassendatei können mit Hilfe der Option -g des Java-Compilers erhalten werden, z.B.
> javac -g BytecodeExample.java
Die Anweisung erzeugt einen zusätzlichen Byte-Block mit zwei Einträgen, für die im Beispiel verwendeten lokalen Variablen i und j der Methode method1, innerhalb der Local-Variable-Tabelle:
00 04 00 10 .. .. .. .. 00 01
00 02 00 1a .. .. .. .. 00 00
Die erste Zeile ist der Eintrag für die lokale Variable i und die zweite Zeile wurde für j vom Compiler erzeugt. Der Wert der Variablen i interessiert zwischen den Code-Bytes 4 und 4+16 der Methode method1 (siehe Disassemblierung von BytecodeExample.class). Die letzten beiden Bytes 00 01 der ersten Zeile stehen für einen Index im Array der lokalen Variablen. Dabei korrespondiert Index 1 mit der lokalen Variablen i. Die vier mit Punkten markierten Bytes stehen für zwei Indizes im Konstantenpool und verweisen damit auf zwei Konstanten, die die Informationen zum Namen und zum Felddeskriptor der Variablen liefern. Für die zweite Byte-Zeile gilt entsprechendes.
Exceptions Methodenbereich Wird die throws-Klausel innerhalb einer Methodendeklaration verwendet, erzeugt der Compiler ein Exceptions-Attribut. Ein Exceptions-Attribut kann im Anschluss an ein Code-Attribut folgen und codiert die Informationen zu den hinter dem Schlüsselwort throws angegebenen Ausnahmen. Ein Exceptions-Attribut wird erzeugt, wenn z.B. die folgende Methodendeklaration verwendet wird:
public static void method1() 
    throws ArithmeticException
ConstantValue Feldbereich Wird eine Konstante im Quelltext mit Hilfe einer Klassenvariablen definiert, erzeugt der Compiler ein ConstantValue-Attribut innerhalb des Feldbereichs der Klassendatei. Dem entsprechenden Eintrag in der Fields-Tabelle werden Bytes angehängt, mit der der Typ und der Wert der Konstanten (Klassenvariable) ermittelt werden können. Z.B. führt die Angabe von
private static final int jconst = 3;
zur Erzeugung der Bytes (ConstantValue-Attribut für das Feld jconst):
.. .. 00 00 00 02 .. ..
Siehe dazu Abschnitt 4.2.4.

Einige Attribute wie z.B. SourceFile-, LineNumberTable- und LocalVariableTable-Attribute sind optional und nicht zwingend notwendig. Ein Klassendatei-Reader einer virtuellen Java-Maschine kann die darin enthaltenen Informationen auslesen oder auch ignorieren. Dagegen sind Code-, ConstantValue- und Exceptions-Attribute für die Interpretierung einer Klassendatei durch eine JVM-Implementierung essentiell. Mit Hilfe der Option -g des Java-Compilers javac kann z.B. das Attribut LocalVariableTable zusätzlich durch den Compiler in die Klassendatei geschrieben werden. Es können auch neue Attribute definiert werden, die durch einen Java-Übersetzer an die entprechenden Stellen innerhalb einer Klassendatei geschrieben werden. Eine Implementierung einer virtuellen Java-Maschine kann nun die Informationen dieser neuen Attribute für bestimmte Zwecke auswerten.

 

 

 

Diese Seite nutzt Google-Dienste - siehe dazu Datenschutz.

Copyright © 2006, 2007 Harald Roeder