Die Architektur der Java VM

SeminarthemenDie Architektur der Java VMI.II.• III. Das class-Dateiformat • IV.V.VI.VII.

III. Das class-Dateiformat

↑ oben

III. 1 Schematischer Aufbau von class-Dateien

Tabellen und Untertabellen

Eine class-Datei besteht grundsätzlich aus ineinander verschachtelten Tabellen. Der Begriff Tabelle ist in diesem Zusammenhang gebräuchlich aber leicht irreführend, da es sich nicht um eine Struktur mit Zeilen und Spalten handelt, sondern um aneinandergereihte Felder, die

Wenn sich eine bestimmte Abfolge von Feldern wiederholt, spricht man auch von einer Untertabelle.

Durch diesen verschachtelten Aufbau mit Feldern variabler Länge lässt sich trotz der grundsätzlich festen Abfolge der Felder fast kein Feld über einen festen Offset vom Dateianfang berechnen. Diese Struktur ist daher kaum geeignet, um performant einen bestimmten Eintrag aufzufinden, weist jedoch einen sehr geringen Daten-Overhead für die Struktur selbst auf.

Beschreibungsformat und Typen

Der Aufbau der einzelnen Tabellen wird in einer Blockdarstellung beschrieben, bei der die Darstellungsgröße eines Blocks fester Länge proportional zu seinem Inhalt (Länge in bytes) ist. Variable Größen werden durch Blöcke mit gestricheltem Rahmen angedeutet. Bei Untertabellen entspricht die Blockbreite der Breite eines Elements. Die Abfolge der Blöcke ist durch die normale Lesereihenfolge – links nach rechts, dann oben nach unten – gegeben. Wenn mehrere Blöcke in einer Zeile dargestellt werden, dient dies nur der Übersicht.

u1[] Länge variiert je nach Inhalt
u1:1 byte
u2:2 byte big-endian
UT, u4 Elemente
u2: entries ↓
UT, verscheiden lange Elemente
u4:4 byte big-endian = 42

Die Tabellen bestehen aus Feldern der Typen u1, u2 und u4. Das u steht für unsigned integer, gibt also eine vorzeichenlosen Wert von 1, 2 oder 4 byte-Länge an, der in big-endian Bytefolge kodiert ist. D.h. das MSB steht ganz links, das LSB ganz rechts. Felder variabler Länge bestehen aus byte[] was dem Typ u1[] entspricht. Wenn ein Feld einen bestimmten Wert haben muss, ist dieser nach einem '='-Zeichen angegeben. Im Beispiel muss das Feld u4 den Wert 42 haben.

↑ oben

III. 2 Die ClassFile-Tabelle

Die ClassFile-Tabelle ist die oberste Struktur einer class-Datei, und beschreibt deren Aufbau:

magic = 0xCAFEBABE
minor_version
major_version
constant_pool_count
ConstantPool
access_flags
this_class→7
super_class→7
interface_count
interface_index[]→7
field_count
Field[]
method_count
Method[]
attribute_count
Attribute[]
magic
Die magische Nummer identifiziert die Datei als class-Datei und hat den hexadezimalen Wert 0xCAFEBABE.
minor_version, major_version
Bilden zusammen die Java-Versions-Nummer der class-Datei major_version.minor_version, die einer JVM Aufschluß über die bei der Erzeugung des Bytecode verwendete Spezifikation gibt.
this_class
Index in den CP auf den CONSTANT_Class-Eintrag, der die Klasse benennt, die durch diese class-Datei beschrieben wird.
super_class
Index in den CP auf den CONSTANT_Class-Eintrag, der die Superklasse von this_class benennt (auch wenn dies Object ist). Nur Objekt selbst hat keine Superklasse und daher den Wert 0. Interfaces verweisen immer auf Object.
interface_index[]
Jedes Array-Element enthält einen Index in den CP auf einen CONSTANT_Class-Eintrag, der ein direktes Superinterface von this_class benennt.
constant_pool_count
Die Anzahl der Einträge im CP dieser Klasse + 1, da die Einträge von 1 bis constant_pool_count-1 indiziert werden.
interface_count, field_count, method_count, attribute_count
Anzahl der Elemente in der direkt gefolgten Untertabelle (nicht die byte-Länge).
Attribute[]
Von der JVM für die ClassFile-Tabelle spezifiziert: InnerClasses, EnclosingMethod, SourceFile, Signature, Deprecated
access_flags
2.1: Zugriffs- und Eingenschafts-Flags von Klassen
NameWertBeschreibung
ACC_PUBLIC0x0001Klasse ist als public deklariert, daher Zugriff auch von außerhalb ihres Pakets erlaubt.
ACC_FINAL0x0010Klasse ist als final deklariert, keine Unterklassen erlaubt.
ACC_SUPER0x0020Besondere Behandlung für Methoden der Superklasse beim Aufruf über invokespecial.
ACC_INTERFACE0x0200class-File beschreibt ein Interface, keine Klasse. ACC_ABSTRACT erforderlich.
ACC_ABSTRACT0x0400Klasse ist als abstract deklariert, daher keine Instanziierung erlaubt.
ACC_SYNTHETIC0x1000Klasse ist vom Compiler generiert, im Quellcode nicht vorhanden.
ACC_ANNOTATION0x2000class-File beschreibt eine Annotation, keine Klasse. ACC_INTERFACE erforderlich.
ACC_ENUM0x4000Diese Klasse (oder ihre Superklasse) ist als Aufzählungstyp enum definiert.

Eine vollständige Klasse sieht deassembliert (also lesbar gemachter Bytecode) so aus: Example. Die Java-Quelldatei ist ebenso im Abschnitt Beispieldateien zu finden.

↑ oben

III. 3 Die ConstantPool-Tabelle

Der bisher mehrfach erwähnte Constant Pool ist die Ausprägung der ConstantPool-Tabelle einer bestimmten Klasse. Er enthält alle Konstanten einer Klasse, wie Namen von Methoden, Feldern und Klassen (Typen), Descriptoren, numerische Konstanten und String-Konstanten. Der schematische Aufbau ist dabei sehr einfach:

tag
constant_value[]
tag
Bestimmt den Typ und damit auch die Länge/Aufbau der Konstante im constant_value-Feld.
constant_value[]
Je nach (durch den tag bestimmten) Typ hat der constant_value-Teil eine feste Länge (byte-Anzahl) mit Ausnahme des Typs CONSTANT_Utf8, bei dem die Länge als erstes Feld length angegeben wird.

Die Spezifikation unterscheidet bisher die Konstanten:

CONSTANT_Utf8
tag = 1
length
bytes[]
CONSTANT_Integer
tag = 3
bytes
Auch short, char, byte und boolean Konstanten werden als CONSTANT_Integer im CP abgelegt.
CONSTANT_Float
tag = 4
bytes
CONSTANT_Long
tag = 5
high_bytes
low_bytes
CONSTANT_Double
tag = 6
high_bytes
low_bytes
CONSTANT_Class
tag = 7
name_index→1
CONSTANT_String
tag = 8
string_index→1
CONSTANT_Fieldref
tag = 9
class_index→7
name_type_index→12
CONSTANT_Methodref
tag = 10
class_index→7
name_type_index→12
CONSTANT_InterfaceMethodref
tag = 11
class_index→7
name_type_index→12
CONSTANT_NameAndType
tag = 12
name_index→1
descriptor_index→1

Die in einem Constant Pool enthaltenen Einträge werden dabei von 1 bis constant_pool_count - 1 druchnummeriert. Bei long- (CONSTANT_Long) und double-Konstanten (CONSTANT_Double) muss die nachfolgende Nummer frei bleiben. Den Grund dafür zeigt später der Runtime Constant Pool. Die mit ?_index benannten Felder sind Indexreferenzen auf andere Einträge des Constant Pools eines bestimmten Typs (Link).

Modifiziertes Utf8-Zeichen-Format

Textkonstanten werden in einem modifizierten Utf8-Format gespeichert, bei dem alle ASCII-Zeichen (außer 0) durch ein byte entsprechend des ASCII-Codes dargestellt werden. Die übrigen Unicode-Zeichen verwenden 2 oder 3 bytes/Zeichen:

Unicode 1 byte = ASCII
Die Zeichen von \u0001 bis \u007F werden mit 7 Datenbits und dem Prefix 0 kodiert:
0
bits 6-0
Unicode 2 byte
Das Nullzeichen \u0000 und die Zeichen von \u0080 bis \u07FF werden mit 11 Datenbits verteilt auf 2 Bytes (a,b) mit den Prefixa 110 und Prefixb 10 kodiert. Der Wert des Zeichens ergibt sich aus: char = ((a & 0x1f) << 6) + (b & 0x3f).
1
1
0
bits 10-6
1
0
bits 5-0
Unicode 3 byte
Die Zeichen von \u0800 bis \uFFF werden mit Datenbits verteilt auf 3 Bytes a,b,c mit dem Prefixa 1110 und dem Prefixb,c 10 kodiert. Der wert des Zeichens ergibt sich aus: char = ((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f).
1
1
1
0
bits 15-12
1
0
bits 11-6
1
0
bits 5-0

Längenangaben für variable Felder, wie etwa length bei einem CONSTANT_Utf8-Eintrag, beziehen sich immer auf die Anzahl der bytes im nachfolgenden Feld variabler Länge, sodass damit das erste byte des nachfolgenden Feldes berechnet werden kann.

↑ oben

III. 4 Interne Namen: Konventionen und Herkunft

Voll qulifizierte Klassennamen

Innerhalb der JVM werden ausschließlich voll qualifizierte Klassen- und Interface-Namen verwendet. Für String wäre es etwa java.lang.String. Anders als in der Java-Sprache werden die Paketpfade intern nicht durch '.' sondern durch '/' unterteilt, so dass der interne Klassennamen von String entsprechend java/lang/String ist. Die Verwendung des '/' anstelle des '.' hat historische Gründe, und ansonsten keine weitere Bedeutung. In diesem Artikel wird in Zukunft nicht mehr expliziet darauf hingewiesen, dass es sich um die interne Darstellung von Klassennamen handelt.

Array-Klassen werden automatisch von der JVM (nicht von einem ClassLoader) erzeugt. Der Name einer Array-Klasse wird aus den Dimensionen des Arrays und seinem Elementtyp gebildet. Dabei werden die auch für Descriptoren verwendeten Abkürzungen eingesetzt. Die Klasse eines Arrays vom Typ int[] heißt daher [I. String[][] ergibt sich zu [[Ljava/lang/String;. Diese Namen wären in der Java-Sprache natürlich illegal, lassen sich aber über arrayObjekt.getClass().getName() erfragen. Daraus folgt auch, dass für jeden Elementtyp und jede Dimension eine eigene Array-Klasse erzeugt wird.

Besondere Namen von Initalisierungs-Methoden (Konstruktoren)

Die Konstruktoren einer Klasse werden vom Java-Compiler in Instanz-Initialisierungsmethoden umgewandelt, die immer <init> heißen, und die selbe Parameterliste und Sichtbarkeit wie der Konstruktor haben, aus dem sie erzeugt wurden. Sie dürfen nur innerhalb der JVM mit dem invokespecial-Befehl aufgerufen werde. Jeder Konstruktor der Java-Klasse Foo

class Foo {
	private Foo(String name, int nr) { ... }
	public  Foo(String name) { this(name, 42); }
}

wird zu einer Initialisierungsmethode:

private void <init>(String name, int nr) { ... }
public  void <init>(String name) { <init>(name, 42); }

Jede Klasse (und jedes Interface) hat außerdem eine vom Compiler generierte statische, parameterlose Klassen- oder Interface Initialisierungsmethode mit dem Namen <clinit>, die implizit von der JVM aufgerufen wird, wenn diese die Klasse initialisiert. Sie kann weder in der Java-Sprache noch auf Bytecode-Ebene explizit aufgerufen werden. Der static-Block der Java-Klasse Bar

class Bar {
	static { /* Anweisungen /* } ...
}

entspricht im Bytecode der Initialisierungsmethode:

static void <clinit>() {
	/* Anweisungen */
}

Einfache Namen für Methoden, Felder und lokale Variablen

Die Namen von Methoden, Feldern und lokale Variablen werden unqualifizierten verwendet. Sie dürfen keine '.', ';', '[' oder '/'-Zeichen enthalten. Methodennamen dürfen zusätzlich auch die Zeichen '<' oder '>' nicht enthalten, mit Ausnahme der speziellen Konstruktor-Methoden <init> und <clinit>.

↑ oben

III. 5 Attribute und Annotations

Attribute

Typische Beispiele sind Debuginformationen, wie Zeilennummern oder der Name der Quellcodedatei, aus welcher der Bytecode erzeugt wurde. Sie gehören aber auch zum grundlegenden Funktionsprinzip der JVM. Etwa die Implementierung oder Exceptions einer Methode oder der Initalwert eines Felders wird als Eintrag in der passenden Attribute-Tabelle eingefügt. Attribute können in der Attribute-Tabelle von

Beim Laden einer Klasse werden die für die JVM spezifizierten Attribute meist in die Laufzeitstrukturen eingearbeitet. Aber es sind auch Attribute erlaubt, die nicht von der JVM spezifiziert sind. Diese haben keine Funktion für die JVM und werden von ihr ignoriert. Der Java-Programmierer kann eigene Attribute über Annotations in den Bytecode einfügen. Diese Attribute können, sofern ihre Annotation selbst entsprechend attributiert wurde, auch über Selbstreflexion ausgelesen werden. Attribute werden oft auch einfach nur als (z.B. so ein "Reflexion ermöglichen"-) Flag verwendet, um Klassen, Feldern oder Methoden eine weitere Eingeschaft zu verleihen, die nicht Teil der Java-Sprache ist, aber zur Laufzeit oder nach dem Decompilieren noch vorhanden sein soll.

Attribute der JVM-Spezifikation

Das wichtigste Attribut der JVM ist wohl das Code-Attribut, welches vor allem die Bytecode-Implementierung einer Methode enthält. Aber auch die Flusskontrolle von Exceptions ist ein Bestandteil dieses Attributes. Die für die JVM spezifizierten Attribute sind teils erforderliche, teils optionale Bestandteile der Ausführung. Einige wurde lediglich zur Vereinheitlichung spezifiziert, da sie keine Funktion für die JVM haben.

6.1:Für die JVM spezifizierte Attribute
NameTabelle(n)Beschreibung
Synthetic Method, Field, ClassFile Markiert Klassen, Felder oder Methoden als vom Compiler generiert. Alternative zum ACC_SYNTHETIC-Flag.
Deprecated Method, Field, ClassFile Markiert Klassen, Felder oder Methoden, die nicht mehr verwendet werden sollen (@Deprecated-Annotation).
Signature Method, Field, ClassFile Verweist auf den Eintrag im RCP der die Signatur der Klasse, Methode oder des Feldes angibt.
RuntimeVisibleAnnotations Method, Field, ClassFile Gibt die Annotations an, die auch zur Laufzeit über Reflexion abgefragt werden können.
RuntimeInvisibleAnnotations Method, Field, ClassFile Gibt die Annotations an, die nicht zur Laufzeit über Reflexion abgefragt werden können.
SourceFile ClassFile Verweist auf den Namen der Quelldatei im RCP, aus welcher der Bytecode der Klasse erzeugt wurde.
InnerClasses ClassFile Beschreibt die Zugriffsrechte und Eingenschaften von inneren Klassen.
EnclosingMethod ClassFile Enthält die Zusatzinformation von lokalen und annonymen Klassen.
SourceDebugExtension ClassFile Einbringen von zusätzlichen Debugtexten. Hat keine Auswinkung aufdie JVM
Code Method Enthält die zur Ausführung der Methode benötigten Informationen: Bytecode-Implementierung, Exception-Tabelle
LineNumberTable Code Kann zum Debuggen angegeben werden, um den Befehlen des Bytecodes einer Methode Zeilennummern im Quellcode zuzuweisen.
LocalVariableTable Code Kann zum Debuggen angegeben werden, um Name und Typinformationen der lokalen Variablen der Methode zu hinterlegen.
Exceptions Method Gibt an, welche Exceptions eine Methode auslösen kann (throws)
RuntimeVisibleParameterAnnotations Method Gibt die Annotation von Methoden-Parametern an, die zur Laufzeit über Reflexion abgefragt werden könne.
RuntimeInvisibleParameterAnnotations Method Gibt die Annotations von lokalen Variablen einer Methode an, die zur Laufzeit über Reflexion abgefragt werden können.
AnnotationDefault Method Gibt default-Wert von Annotation-Methoden an, der über Reflexion ausgelesen werden kann.
ConstantValue Field Verweist zur Initialisierung eines Feldes, abhängig vom Typ des Feldes, auf eine entsprechende Konstante im CP.

Annotations

Annotations sind selbst kein Teil der JVM-Spezifikation. Sie gehören zur Java-Language-Specification (JLS) und ermöglichen dem Java-Programmierer, selbst Attribute an Klassen, Felder oder Methoden anzuhängen, indem die Annotation im Java-Quellcode oberhalb des attributierten Elements notiert wird. Annotations werden im Format @Name[(Parameterliste)] angegeben, wie in diesen Beispielen:

@SuppressWarnings("unchecked")
class { 
	@Deprecated
	int doStupidThings(String withThat) { 
		Thread likeThis = (Thread)withThat; 
		return -1;
	}
}

Annotations können auch für einzelne Methoden-Parameter, oder lokale Variablen angegeben werden. Oft dienen sie (wie hier @Deprecated) einfach als Flag, um ein Element mit einer bestimmten Eingenschaft zu kennzeichnen. Die JLS definiert selbst drei Annotations, die den meisten Programmierern bekannt sind:

6.2:Durch die JLS definierte Annotations
@NameWert(e)Beschreibung
@Deprecated-Markiert Klassen, Felder oder Attribute die nicht mehr verwendet werden sollten.
@Override-Informiert dem Compiler, dass eine Methode meint, eine Methode einer Superklasse zu überschriebt.
@SuppressWarningsString[] valueInformiert den Compiler, dass er die unter value benannten Warnungen für das markierte Element nicht mehr ausgeben soll.

Eigene Annotations werden analog zu Klassen oder Interfaces erstellt. Ein NameWert-Paar

Annotationen sind statische Zusatzdaten, weshalb auch der Werttyp auf die im RCP möglichen Typen beschränkt werden musste.
Mit der Annotation des Beispiels können Methoden mit einer Laufzeitkomplexität und einem zusätzlichen, optionalen Hinweis versehen werden.

@Retention(RetentionPolicy.RUNTIME)       // Annotation zur Laufzeit über Reflexion nutzen
@Target(ElementType.METHOD)               // Annotation ist nur für Methoden
public @interface RuntimeComplexity {
	enum Complexity { On, On2, O1 };  // usw.
	Complexity complexity();          // das Feld, welche den jeweiligen konstanten 
	String     hint() default "";     // Wert enthält ist implzit enthalten
}

Die erstellte Annotation kann nun analog zu den erwähnten verwendet werden:

	@RuntimeComplexity(Complexity.O1)
	int getLength() {
		return this.length;
	}
	@RuntimeComplexity(Complexity.On, "average")
	int countAIn(String that) {
		int result = 0;
		for (int i = 0; i < that.length(); i++)
			if (that.charAt(i) == 'A')
				++result;
		return result;
	}

Für die Verarbeitung von Annotations, die über Reflexion abgefragt werden können, enthält das JDK seit Version 6 das Annotation Processing Tool (APT).

↑ oben

III. 6 Descriptoren und Signaturen

Bedeutung und Unterschied

Den meisten ist aus der Java-Sprache die sogenannte Methoden-Signatur bekannt. Sie setzt sich aus dem Namen der Methode und ihren Parametern zusammen. Jede Methoden-Signatur muss Klassenweit eindeutig sein. Methoden mit gleichem Namen aber unterschiedlichen Parametern nennt man überladen. Die Methoden-Signatur ist vorallem entscheidend für das Dynamische Binden von Methoden. Da dies zur Laufzeit geschieht, müssen der JVM entsprechende Informationen über jede Methode vorliegen, um die entsprechend richtige auswählen zu können. Für jede Methode einer Klasse enthält daher der Constant Pool der Klasse ein NameAndTyp-Eintrag der zum einen auf den Methodennamen verweist, und zum anderen auf einen Descriptor.

Anders als es zunächst naheliegend erscheint beschreiben Descriptoren und nicht Signaturen die Parameter einer Methode. Signaturen sind Attribute und wurde erst mit Java 1.5 durch die Einführung von Generics zur JVM-Spezifikation hinzugefügt. Generics sind in Java durch eine Type Erasure genannte Technik implementiert. Dabei wird der generische Typ beim compilieren "gelöscht", und durch Casts und Brücken-Methoden (Bridge-Methoden) ersetzt. Der Descriptor einer Methode enthält also niemals Generics. Die druch Type Erasure verlorenen Typinformationen der Generics müssen jedoch für zur Unterstützung von Refelections weiterhin bekannt sein. Daher wird ein Signatur-Attribut zur Attribut-Tabelle jeder generischen Methode hinzugefügt, dass eine vollständige Beschreibung der Paramter enthält. Neben den Methoden-Signaturen werden auch die Typen von Feldern (in Java-Sprache auch Attribute genannt) mit Descriptoren beschrieben. Auch hier findet ggf. Type Erasure statt.

Grammatik

Signaturen und Descriptoren verwenden prinzipiel die gleiche Grammatik. Für Signaturen wurde diese lediglich um einige Konstrukte/Definitionen erweitert. Der Aufbau wird hier durch eine einfach abstrakte Syntax (ähnlich der BNF und RegEx-Ausdrücken) dargestellt: '|' steht für die Alternative, '*' für 0-∞ Wiederholungen, '+' für 1-∞ Wiederholungen, '?' für eine Option (0-1), Fettgedrucke Zeichen sind Terminalsymbole und entsprechen Unicode-Zeichen. Jeder Bezeichner wird durch eine Formel Bezeichner = Aufbau beschrieben.

Field-Descriptoren

Die einfachsten Descriptoren sind FieldDescriptoren, die den Typ eines Feldes beschreiben (in der Java-sprache oft auch Attribut genannt).

FieldDescriptor = FieldType

ComponentType   = FieldType
FieldType       = BaseType | ObjectType | ArrayType
BaseType        = B | C | D | F | I | J | S | Z
ObjectType      = LClassname;
ArrayType       = [ComponentType

Die Tabelle 5.1 erklärt die Bedeutung der konstanten Zeichen:

5.1: Bedeutung der BaseType-Zeichen
BaseType-ZeichenTypBedeutung
Bbytevorzeichenbehafteter Bytewert
CcharUnicode-Zeichen
DdoubleFließkommawert mit doppelter Genaigkeit
FfloatFließkommawert mit einfacher Genauigkeit
Iintvorzeichenbehafteter Integerwert
Jlongvorzeichenbehafteter Longwert
LClassname;referenceEine Instanz der Klasse Classname
Sshortvorzeichenbehafteter Shortwert
Vvoidentspricht dem Rückgabewert void
Zbooleantrue oder false
[referenceEine Array-Dimension

Der Descriptor einer Instanzvariable vom Typ int ist z.B. einfach I. Der einer Variable vom Typ String wäre Ljava/lang/String;. Ein eindimensionales Array mit dem Elementtyp boolean entspricht [Z und ein zweidimensionales Array von Objects wird so [[Ljava/lang/Object; beschrieben.

Method-Descriptoren

Ein MethodeDescriptor beschreibt die Parameter einer Methode und deren Rückgabewert, auch wenn dieser nicht zur Methode-Signatur in der Jva-Sprache gehört. Der Name der Methode ist im MethodeDescriptor nicht enthalten. Ein ParameterDescriptor beschreibt entsprechend einen Parameter der Methode, der ReturnDescriptor ihren Rückgabewert.

MethodeDescriptor   = ( ParameterDescriptor* ) ReturnDescriptor

ParameterDescriptor = FieldDescriptor
ReturnDescriptor    = FieldDescriptor | VoidDescriptor
VoidDescriptor      = V

Die Anzahl der Parameter eines MethodeDescriptor darf maximal 255 betragen, da der Zugriff später über die lokalen Variablen eines Frames geschieht, und die ?load-Befehle jeweils nur mit einem byte als Indexwert parametisiert werden können. Dabei zählen long- und double-Werte ebenfalls doppelt, da sie auch 2 lokale Variablen belegen.

Der MethodeDescriptor der Java-Methode:

Object beispielMethode(int nr, String name, long[][] werte);

sieht so aus:     (ILjava/lang/String;[[J)Ljava/lang/Object;

Dies ist unabhängig davon, ob die Methode in einer Klasse oder einem Interface definiert wurde, statisch oder nicht statisch ist.

Signaturen

Signaturen werden verwendet, um Typinformationen der Java-Programmiersprache zu hinterlegen, die nicht Teil des JVM-Typsystems sind, wie etwa generische Typen von Feldern oder Parametern oder parametrisierte Methoden. Diese Informationen müssen vorhanden sein, um die verlorenen Typen wiederzuerhalten, wenn diese per Reflektion ausgelesen werden. Zudem sind sie für Debugger nützlich.

Ein Compiler der Java-Bytecode erzeugt, muss für Klassen, Interfaces, Konstruktoren, Felder und Methoden, die generische Typen enthalten, entsprechende Signaturen erzeugen. Diese werden bisher noch nicht beim Laden oder Linken einer Klasse auf eine korrekte Form überprüft. Dies geschieht wenn diese zum ersten mal über Reflection ausgelesen werden. In zukünftigen JVM-Spezifikation kann die Prüfung jedoch beim Laden oder Linken vorgeschrieben sein.

In der Grammatik werden Identifier verwendet, die dem einfachen Namen von Typen, Feldern, lokalen Variablen, Parametern oder Methoden entsprechen, wie sie der compiler generiert. Diese können auch Zeichen enthalten, die in der Java-Sprache nicht für Namen verwendet werden dürfen.

Class-Signaturen

Ein FormalTypeParameter wird durch seinen Namen, seine Klassse und die von ihm implementierten Schnittstellen beschrieben. Wenn er keine Klasse hat, wird diese zu Objekt angenommen.

FormalTypeParameters     = < FormalTypeParameter+ >

FormalTypeParameter      = Identifier ClassBound InterfaceBound*
ClassBound               = : FieldTypeSignature?
InterfaceBound           = : FieldTypeSignature

Eine ClassSignature beschreibt Klassen- und Interface-Typen mit ihrem voll qualifizierten Namen und formalen Typparameter (sofern vorh.), und gibt deren Superklasse und Superinterfaces (sofern vorh. inkl. Typparameter) an. Die Signatur muss so formatiert sein, dass sie dem vollständigen Namen der Klasse entspricht, wenn alle Typargumente entfernt und alle '.' durch '$'-Zeichen ersetzt werden.

ClassSignature           = FormalTypeParameters? SuperclassSignature SuperinterfaceSignature*

SuperclassSignature      = ClassTypeSignature
SuperinterfaceSignature  = ClassTypeSignature
ClassTypeSignature       = L PackageSpecifier* SimpleClassTypeSignature ClassTypeSignatureSuffix* ;
PackageSpecifier         = Identifier / PackageSpecifier*
SimpleClassTypeSignature = Identifier TypeArguments?
ClassTypeSignatureSuffix = . SimpleClassTypeSignature

Field-Signaturen

Eine FieldTypeSignature beschreibt die möglicherweise mit Typparametern versehehenen Typen von Feldern, Parametern und lokalen Variablen.

FieldTypeSignature       = ClassTypeSignature | ArrayTypeSignature | TypeVariableSignature

TypeVariableSignature    = T Identifer ;
TypeArguments            = < TypeArgument+ >
TypeArgument             = WildcardIndicator? FieldTypeSignature | *
WildcardIndicator        = + | -
ArrayTypeSignature       = [ TypeSignature
TypeSignature            = [ FieldTypeSignature | [ BaseType

Method-Signaturen

Eine MethodTypeSignature gibt die formalen Argumente, Exceptions (im throws-Teil), den Rückgabewert und die formalen Typen einer Methode inkl. ihrer Typparameter (generischen Typen) an.

MethodTypeSignature      = FormalTypeParameters? ( TypeSignature* ) ReturnType ThrowsSignature*

ReturnType               = TypeSignature | VoidDescriptor 
ThrowsSignature          = ^ ClassTypeSignature | ^ TypeVariableSignature

Wenn der throws Teil einer Methode keine Typvariablen (Generics) enthält, ist die ThrowsSignature der MethodTypeSignature optional.

↑ oben

III. 7 Die Field- und Method-Tabelle

Der Aufbau der Field- und Method-Tabelle ist identisch, und relativ einfach gehalten:

access_flags
name_index→1
descriptor_index→1
attribute_count
Attribute[]
name_index
Index in den (R)CP, der dort den Namen des Feldes bzw. der Methode enthält.
descriptor_index
Index in den (R)CP, der dort den Descriptor des Feldes bzw. der Methode enthält.
attribte_count
Die Anzahl der Einträge in der nachfolgenden Attribute-Tabelle.
Attribute[]
Wichtig ist vorallem das Code-Attribut für Methoden.
Alle von der JVM spezifizierten Attribute für Felder und Methoden sind im Abschnitt Attribute der JVM zu finden.
access_flags
7.1: Zugriffs- und Eingenschafts-Flags von Feldern
NameWertBeschreibung
ACC_PUBLIC0x0001Feld ist als public deklariert, daher Zugriff auch von außerhalb des Pakets erlaubt.
ACC_PRIVATE0x0002Feld ist als private deklariert, daher Zugriff nur in der definierenden Klasse.
ACC_PROTECTED0x0004Feld ist als protected deklariert, daher Zugriff auch in Unterklassen der definierenden Klasse.
ACC_STATIC0x0008Feld ist als static deklariert, also eine Klassenvariable (ohne) oder Konstante (mit) ACC_FINAL.
ACC_FINAL0x0010Feld ist als final deklariert, daher keine weitere Zuweisung nach Initalisierung möglich.
ACC_VOLATILE0x0040Feld ist als volatile deklariert, daher kein cachen des Werts möglich.
ACC_TRANSIENT0x0080Feld ist als transient deklariert, daher kein lesender oder schreibender Zugriff vom PersistentObjectManager.
ACC_SYNTHETIC0x1000Feld ist vom Compiler generiert, im Quellcode nicht vorhanden.
ACC_ENUM0x4000Feld wird verwendet, um einen Wert eines Aufzählungstyp zu speichern.
7.2: Zugriffs- und Eingenschafts-Flags von Methoden
NameWertBeschreibung
ACC_PUBLIC0x0001Methode ist als public deklariert, daher Aufruf auch von außerhalb des Pakets erlaubt.
ACC_PRIVATE0x0002Methode ist als private deklariert, daher Aufruf nur in der definierenden Klasse möglich.
ACC_PROTECTED0x0004Methode ist als protected deklariert, daher Aufruf auch in Unterklassen der definierenden Klasse erlaubt.
ACC_STATIC0x0008Methode ist als static deklariert, also eine Klassenmethode.
ACC_FINAL0x0010Methode ist als final deklariert, kann nicht überschrieben werden.
ACC_SYNCHRONIZED0x0020Methode ist als synchronized deklariert, daher Aufruf über einen Monitor.
ACC_BRIDGE0x0040Methode wurde als Bridge-Methode (Type Erasure) vom Compiler erzeugt.
ACC_VARARGS0x0080Methode wurde im Quellcode mit variabler Parameteranzahl deklariert.
ACC_NATIVE0x0100Methode ist als native deklariert, wird daher in einer anderen Sprache als Java-Bytecode implementiert.
ACC_ABSTRACT0x0400Methode ist als abstract deklariert, besitzt daher keine Implementierung.
ACC_STRICT0x0800Methode ist als strictfp deklariert, verwendet daher den Gleitkomma-Modus FP-strict.
ACC_SYNTHETIC0x1000Methode ist vom Compiler generiert, im Quellcode nicht vorhanden.
↑ oben

III. 8 Die Exception-Tabelle

Enthält die Informationen zur Steuerung des Kontrolltransfers beim Auftreten von Exceptions. Für jeden unterschiedlichen Exception-Typ, der innerhalb einer Methode gefangen werden soll, wird jeweils ein Eintrag in der Exceptions-Tabelle des Code-Attributes erstellt, der so aufgebaut ist:

start_pc
end_pc
handler_pc
catch_type→7
start_pc, end_pc
Markieren den Bereich im code-Array einer Methode, in dem Exceptions vom catch_type gefangen werden. Beide Werte geben einen byte-Offset vom Methodenanfang an. Der start_pc ist inclusiv und muss auf einen Opcode verweisen, der von end_pc exclusiv und muss auf einen Opcode oder das Ende der Methode verweisen.
handler_pc
Markiert den Opcode im code-Array, bei dem die Ausführung beim fangen der Exception fortgesetzt wird. handler_pc muss auf einen Opcode verweisen.
catch_type
Ein gültiger Index in den (R)CP, der die Exception-Klasse angibt, die gefangen wird (natürlich auch davon abgeleitete Klassen). Wenn catch_type 0 gesetzt wird, werden alle Exceptions gefangen. Dies wird für die finally-Implementierung verwendet.
↑ oben

III. 9 Die Attribute-Tabelle

Die Bedeutung von Attributen wurde bereits einige Abschnitte zuvor beschrieben. An dieser Stelle geht es um den tatsächlichen Aufbau der einzelnen Attribute. Allgemein bestehen Attribute aus den Feldern:

name_index→1
length
info[]
name_index
Index in den (R)CP als Verweis auf den Namen des Attributs.
length
Länge der eigentlichen Attributdaten (exklusive name_index und length) = Länge des info[]-Arrays.
info[]
Die eigentlichen Attributdaten (allgemein betrachtet ein byte[]). Die Länge variiert je nach Attribut-Typ und dessen Werten und kann auch 0 betragen, wenn ein Attribut etwa als Flag verwendet wird.

Die Bedeutung der Felder name_index und length gilt für alle Attribute, und wird daher nicht wiederholt angegeben. Auf die Angabe des jeweiligen Attributnamens im Feld – name_index = Name – wird aus Platzgründen verzichtet. Der Fokus der Betrachtung liegt in den folgenden Abschnitten auf dem Aufbau des info[]-Feldes der verschiedenen Attribute.

Attribute mit fester Länge

Synthetic
name_index→1
length = 0
Deprecated
name_index→1
length = 0
Signature
name_index→1
length = 2
signature_index→1
SourceFile
name_index→1
length = 2
sourcefile_index→1
ConstantValue
name_index→1
length = 2
constant_value_index
9.1: Attribut ConstantValue Typen-Index
attributierter Feld-Typconstant_value_index→RCP-Eintrag
longCONSTANT_Long →5
floatCONSTANT_Float →4
doubleCONSTANT_Double →6
int,short,char,byte,booleanCONSTANT_Integer →3
StringCONSTANT_String →8

Das Code-Attribut

name_index→1
length
max_stack
max_locals
code_length
code
exceptions_count
Exception[]
attribute_count
Attribute[]
max_stack
Die maximale Häöhe des Operand-Stack bei der Verarbeitung der Methode.
max_locals
Die Anzahl der maximal verwendeten lokalen Variablen.
code_length
Byte-Anzahl der Methoden-Implementierung.
code
Implementierung (Bytecode) der Methode (ein byte[]).
exception_count, attribute_count
Anzahl der Einträge in der nachfolgenden Attributes- bzw. Exceptions-Tabelle.
Attribute[]
Von derJVM für das Code-Attribut spezifiziert: LineNumberTable, LocalVariableTable

Das Exceptions-Attribut

Das Exceptions-Attribut wird allen Methoden hinzugefügt, die Exceptions auslösen können, die nicht von RuntimeException oder Error abgeleitet wurden. Eine Methode sollte derartige Exceptions nur auslösen, wenn diese (oder eine Oberklasse) in der vom Attribut beschrieben Tabelle enthalten sind. Dies wird jedoch nur vom Compiler überprüft. Diese Tabelle ist keine essentieller Bestandteil des Exception-Kontrolltransfer-Mechanismus der JVM.

name_index→1
length
number_of_exceptions
exception_index[]→7
number_of_exceptions
Gibt die Anzahl der Einträge im exception_index-Array an.
exception_index[]
Jeder Eintrag entspricht einem Index in den (R)CP, der dort eine der Exception-Klassen enthält, die von der Methode ausgelöst werden können.
→ weiter…

SeminarthemenDie Architektur der Java VMI.II.• III. Das class-Dateiformat • IV.V.VI.VII.