Seminarthemen→
Die Architektur der Java VM→
I.•
II.•
III. Das class
-Dateiformat •
IV.•
V.•
VI.•
VII.
class
-Dateiformatclass
-Dateien
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
byte
-Länge haben.Wenn sich eine bestimmte Abfolge von Feldern wiederholt, spricht man auch von einer Untertabelle.
byte
-Länge) ist im Feld direkt vor der UT angegegen.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.
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 byte
s) 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.
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.
ClassFile
-Tabelle
Die ClassFile
-Tabelle ist die oberste Struktur einer class
-Datei, und beschreibt deren Aufbau:
class
-Datei und hat den hexadezimalen Wert 0xCAFEBABE
.class
-Datei major_version.minor_version
, die einer JVM Aufschluß über die bei der Erzeugung des Bytecode
verwendete Spezifikation gibt.CONSTANT_Class
-Eintrag, der die Klasse benennt, die durch diese class
-Datei beschrieben wird.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
.CONSTANT_Class
-Eintrag, der ein direktes Superinterface von this_class
benennt.constant_pool_count
-1 indiziert werden.byte
-Länge).ClassFile
-Tabelle spezifiziert: InnerClasses
, EnclosingMethod
, SourceFile
, Signature
, Deprecated
Name | Wert | Beschreibung |
---|---|---|
ACC_PUBLIC | 0x0001 | Klasse ist als public deklariert, daher Zugriff auch von außerhalb ihres Pakets erlaubt. |
ACC_FINAL | 0x0010 | Klasse ist als final deklariert, keine Unterklassen erlaubt. |
ACC_SUPER | 0x0020 | Besondere Behandlung für Methoden der Superklasse beim Aufruf über invokespecial . |
ACC_INTERFACE | 0x0200 | class -File beschreibt ein Interface, keine Klasse. ACC_ABSTRACT erforderlich. |
ACC_ABSTRACT | 0x0400 | Klasse ist als abstract deklariert, daher keine Instanziierung erlaubt. |
ACC_SYNTHETIC | 0x1000 | Klasse ist vom Compiler generiert, im Quellcode nicht vorhanden. |
ACC_ANNOTATION | 0x2000 | class -File beschreibt eine Annotation, keine Klasse. ACC_INTERFACE erforderlich. |
ACC_ENUM | 0x4000 | Diese 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.
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:
constant_value
-Feld.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:
short
, char
, byte
und boolean
Konstanten werden als CONSTANT_Integer
im CP abgelegt.
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).
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 byte
s/Zeichen:
\u0001
bis \u007F
werden mit 7 Datenbits und dem Prefix 0
kodiert:\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).
\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)
.
Längenangaben für variable Felder, wie etwa length
bei einem CONSTANT_Utf8
-Eintrag, beziehen sich immer auf die Anzahl der byte
s im nachfolgenden Feld variabler Länge, sodass damit das erste byte
des nachfolgenden Feldes berechnet werden kann.
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.
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 */
}
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>
.
Bytecode
einzubringen.Attribute
-Tabellen zusammengefasst.
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
ClassFile
-Tabelle),Fileds
-Tabelle),Method
-Tabelle), oder derAttribute
-Tabellen selbst vorkommen.
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.
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.
Name | Tabelle(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 Bytecode s 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 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:
@Name | Wert(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. |
@SuppressWarnings | String[] value | Informiert 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 Name→Wert-Paar
public Wert-Typ name();
dargestellt.default
-Wert erhalten (Paar muss dann nicht mehr angegeben werden)String
, Class
und Aufzählungstypen sowie Arrays dieser Typen verwenden. Der Typ darf außerdem selbst eine Annotation sein (Schachtelung möglich).
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).
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.
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 FieldDescriptor
en, 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:
BaseType -Zeichen | Typ | Bedeutung |
---|---|---|
B | byte | vorzeichenbehafteter Bytewert |
C | char | Unicode-Zeichen |
D | double | Fließkommawert mit doppelter Genaigkeit |
F | float | Fließkommawert mit einfacher Genauigkeit |
I | int | vorzeichenbehafteter Integerwert |
J | long | vorzeichenbehafteter Longwert |
LClassname; | reference | Eine Instanz der Klasse Classname |
S | short | vorzeichenbehafteter Shortwert |
V | void | entspricht dem Rückgabewert void
|
Z | boolean | true oder false |
[ | reference | Eine 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 Object
s 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 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.
Field
- und Method
-Tabelle
Der Aufbau der Field
- und Method
-Tabelle ist identisch, und relativ einfach gehalten:
Attribute
-Tabelle.Name | Wert | Beschreibung |
---|---|---|
ACC_PUBLIC | 0x0001 | Feld ist als public deklariert, daher Zugriff auch von außerhalb des Pakets erlaubt. |
ACC_PRIVATE | 0x0002 | Feld ist als private deklariert, daher Zugriff nur in der definierenden Klasse. |
ACC_PROTECTED | 0x0004 | Feld ist als protected deklariert, daher Zugriff auch in Unterklassen der definierenden Klasse. |
ACC_STATIC | 0x0008 | Feld ist als static deklariert, also eine Klassenvariable (ohne) oder Konstante (mit) ACC_FINAL . |
ACC_FINAL | 0x0010 | Feld ist als final deklariert, daher keine weitere Zuweisung nach Initalisierung möglich. |
ACC_VOLATILE | 0x0040 | Feld ist als volatile deklariert, daher kein cachen des Werts möglich. |
ACC_TRANSIENT | 0x0080 | Feld ist als transient deklariert, daher kein lesender oder schreibender Zugriff vom PersistentObjectManager . |
ACC_SYNTHETIC | 0x1000 | Feld ist vom Compiler generiert, im Quellcode nicht vorhanden. |
ACC_ENUM | 0x4000 | Feld wird verwendet, um einen Wert eines Aufzählungstyp zu speichern. |
Name | Wert | Beschreibung |
---|---|---|
ACC_PUBLIC | 0x0001 | Methode ist als public deklariert, daher Aufruf auch von außerhalb des Pakets erlaubt. |
ACC_PRIVATE | 0x0002 | Methode ist als private deklariert, daher Aufruf nur in der definierenden Klasse möglich. |
ACC_PROTECTED | 0x0004 | Methode ist als protected deklariert, daher Aufruf auch in Unterklassen der definierenden Klasse erlaubt. |
ACC_STATIC | 0x0008 | Methode ist als static deklariert, also eine Klassenmethode. |
ACC_FINAL | 0x0010 | Methode ist als final deklariert, kann nicht überschrieben werden. |
ACC_SYNCHRONIZED | 0x0020 | Methode ist als synchronized deklariert, daher Aufruf über einen Monitor. |
ACC_BRIDGE | 0x0040 | Methode wurde als Bridge-Methode (Type Erasure) vom Compiler erzeugt. |
ACC_VARARGS | 0x0080 | Methode wurde im Quellcode mit variabler Parameteranzahl deklariert. |
ACC_NATIVE | 0x0100 | Methode ist als native deklariert, wird daher in einer anderen Sprache als Java-Bytecode implementiert. |
ACC_ABSTRACT | 0x0400 | Methode ist als abstract deklariert, besitzt daher keine Implementierung. |
ACC_STRICT | 0x0800 | Methode ist als strictfp deklariert, verwendet daher den Gleitkomma-Modus FP-strict. |
ACC_SYNTHETIC | 0x1000 | Methode ist vom Compiler generiert, im Quellcode nicht vorhanden. |
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:
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.
code
-Array, bei dem die Ausführung beim fangen der Exception fortgesetzt wird. handler_pc
muss auf einen Opcode verweisen.catch_type
0 gesetzt wird, werden alle Exceptions gefangen. Dies wird für die finally
-Implementierung verwendet.Attribute
-TabelleDie 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
und length
) = Länge des info[]
-Arrays.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.
attributierter Feld-Typ | constant_value_index →RCP-Eintrag |
---|---|
long | CONSTANT_Long →5 |
float | CONSTANT_Float →4 |
double | CONSTANT_Double →6 |
int,short,char,byte,boolean | CONSTANT_Integer →3 |
String | CONSTANT_String →8 |
Code
-AttributByte
-Anzahl der Methoden-Implementierung.Bytecode
) der Methode (ein byte[]
).Code
-Attribut spezifiziert: LineNumberTable
, LocalVariableTable
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.
exception_index
-Array an.
Seminarthemen→
Die Architektur der Java VM→
I.•
II.•
III. Das class
-Dateiformat •
IV.•
V.•
VI.•
VII.