Linkertechniken
... [ Seminar WWW und JAVA] ... [ Thema Java Virtual Machine ] ... [ Compilertechniken ] ...
Übersicht: Linkertechniken
Grober Ablauf
- Javacode (.java) wird durch den Javacompiler compiliert
- daraus entsteht der Java-Bytecode im
Class
-Fileformat (.class)
- dieser kann entweder lokal lagern oder übers Netz geladen werden
- Wird die Java Virtual Machine mit einer Klasse gestartet, so wird der Bytecode der Klasse durch den
ClassLoader
geladen.
- Danach wird der Code durch den Bytecode Verifier überprüft,
- benötigte Klassen werden dazugelinkt.
- nun kann der Code durch die Java Virtual Machine ausgeführt werden.
Dies kann entweder geschehen indem die Java Virtual Machine den Code interpretiert,
oder aber durch einen "Just in Time"-Compiler in Maschinencode umwandelt, der dann direkt ausgeführt werden kann.
Überlick: Start-up der Virtual Machine
- Aufruf der Java Virtual Machine
java HelloWorld
Wenn die Java Virtual Machine mit einem Klassennamen gestartet wird, so versucht sie die main
-Methode der Klasse auszuführen.
Hierbei wird sie beim ersten Mal feststellten, dass die Klasse noch nicht geladen ist, und veranlasst dann die folgenden Aktionen.
- Laden der Klasse HelloWorld durch den
ClassLoader
Die benötigte Klasse wird vom ClassLoaders
geladen.
- Linken: Verifikation: Bytecode Verifier,
Preparation: Anlegen von Klassenvariablen
(Resolution: Überprüfen der Symbolischen Referenzen)
- Initialisieren (der Klassenvariablen)
- Ausführen von
main
Laden des Codes
Laden bezeichnet den Prozess die binäre Form einer bestimmten Klasse oder Schnittstelle zu finden und der JVM zur Verfügung zu stellen.
Die binäre Form einer Klasse oder Schnittstelle liegt im Class
Dateiformat vor.
Der Ladeprozeß ist in der Klasse ClassLoader
und deren Subklassen implementiert. Es gibt verschiedene Subklassen, in denen verschiedene Ladestrategien implementiert sind.
z.B. Laden über ein Netzwerk NetworkClassLoader
bzw. AppletClassLoader
(Netscape)
Linking
Linking bezeichnet das Integrieren einer Klasse oder Schnittstelle in die Laufzeitumgebung der Java Virtual Machine.
Das Linken besteht aus den drei Aktivitäten:
- Überprüfung: siehe Bytecode Verifier
- Vorbereitung: Beinhaltet das Anlegen der Klassenvariablen
static int a;
Diese werden mit Standard-Werten gefüllt z.B.:
- int 0
- long 0L
- boolean false
- Auflösung der symbolischen Referenzen
Wann die Referenzen überprüft werden, ist abhängig von der Implementierung:
Referenzen werden entweder an dieser Stelle oder erst bei ihrer direkten Benutzung überprüft. Dies wird auch als "lazy resolution" bezeichnet.
Bevor eine Referenz benutzt werden kann, muß überprüft werden, ob sie gültig ist.
Initialisierung
Initialisierung der Klassenvariablen, z.B. int a = 10;
Bevor eine Klasse initialisiert werden kann, muß ihre Superklasse initialisiert werden.
Jedem Objekt ist ein Status zugeordnet, der den Stand der Initialisierung anzeigt:
- verifiziert und vorbereitet, aber noch nicht initialisiert.
- Wird von Thread T initialisiert.
- fertig initialisiert.
- fehlerhaft.
Ausserdem ist jedem Objekt ein Lock zugeordnet(siehe Multithreading).
Die Initialisierung sieht im Detail wie folgt aus:
- Synchronisieren auf das zu initialisierende Objekt durch setzen des Locks auf das Objekt.
- Ist der Status des Objekt "Wird von Thread T initialisiert" und T ist nicht der eigene Thread, dann warte, bis der andere Thread fertig ist.
- Ist der Status des Objekt "Wird von Thread T initialisiert" und T ist der gleiche Thread, dann liegt eine Rekursion vor.
Dies kann bei folgender Konstellation vorkommen. Ist dies der Fall, so löse den Lock und fahre fort.
- Ist der Status des Objekts "fertig initialisiert", dann löse den Lock und fahre fort.
- Ist der Status des Objekts "fehlerhaft", so löse den Lock und löse die Exception
NoClassDefFoundError
aus.
- Ansonsten setze den Status "Wird von Thread T initialsiert" mit T als eigenem Thread. Löse den Lock.
Initialisere gegebenenfalls die vorhandene Superklasse, falls nötig.
- Nehme die eigentliche Initialisierung vor:
Initialisiere die Klassenvariablen in der Reihenfolge:
final static
Variablen
- Klassenvariablen mit Compilezeitkonstanten, wie
int a = 10;
- restliche Klassenvariablen
- Tritt bei der Initialisierung ein Fehler auf, so löse die ensprechende Exception aus, locke das Objekt und setze den Status des Objekts auf "fehlerhaft" und löse den Lock anschliessend.
- Wird die Initialisierung fehlerfrei durchlaufen, so locke das Objekt, setze den Status auf "fertig initialisiert", löse den Lock und fahre normal fort.
Beispiel für eine mögliche Rekursion bei der Initialisierung
class a {
static int x = b. getx();
}
class b extends a{
public int getx() { ... }
}
Erstellung einer Klasseninstanz
.
.
.
MyClass x = new MyClass ();
.
.
.
- Allokation von Speicher für die Instanzvariablen der Klasse, sowie der Superklassen.
- Initialisieren aller Variablen, auch die der Superklassen mit Standardwerten.
- Rufe den Konstruktor mit Parametern auf:
- Initialisiere die Parametervariablen.
- Führe den Konstruktor aus.
- Rückgabe der Referenz auf die neue Instanz.
Finalization einer Klasseninstanz
class MyClass{
.
.
.
void finalize() {
// Aufräumen z.B. Schließen von Dateien
.
.
.
}
}
- Bevor der Speicher für ein Objekt durch die Garbage Collection freigegeben wird, wird die JVM den Finalizer eines Objekts aufrufen.
- Der finalize-Methode einer Klasse bietet die Möglichkeit Resourcen freizugeben, die der Garbage Collector nicht freigeben kann. z.B. Filedeskriptoren, Grafikkontext.
- Die JVM führt den Finalizer aus, bevor der Speicher für ein Objekt durch die Garbage Collection freigegeben wird.
- Es ist nicht sicher, von welchem Thread und wann der Finalzer ausführt wird.
Da nicht sicher ist, wann eine finalize-Methode vom Garbage Collector ausgeführt wird, sollte sie nicht dazu genutzt werden,
um ein mehrmals benötigten Filedeskriptor freizugeben, da es bei einem erneuten Zugriff auf die gleiche Datei zu einem Fehler
führen kann, wenn der Finalizer noch nicht ausgeführt wurde.
Beenden der Virtual Machine
Die JVM wird beendet, wenn eine der folgenden Ereignisse eintritt:
- Alle Threads sind beendet.
Mit Ausnahme von Dämon-Threads.
- Ein Thread ruft die exit-Methode der Klasse Runtime oder System auf.
Datenstrukturen zur Laufzeit
- Heap
- Auf ihn können alle Threads zugreifen.
- Wird zur Startzeit der JVM angelegt.
- In ihm werden alle Klasseninstanzen und Arrays angelegt.
- Method Area
- Wird von allen Threads geteilt.
- Wird zur Startzeit der JVM angelegt.
- Kann logisch zum Heap gehören und Garbage Collected sein.
(Abhängig von der Implementierung)
- Beinhaltet für jede geladene Klasse:
- Constant Pool
- Ist die Laufzeitentsprechung zur Constant-Pool-Tabelle im
class
-File der Klasse.
- Enthält alle Konstanten, wie numerische Literale, Strings, Methoden- und Feldreferenzen.
- Code der Methoden einer Klasse.
- Feldtabelle
- Enthält Referenz auf Feld im Heap
- Information zur Zugriffstufe z.B. public, protected, etc.
- Datenstrukturen pro Thread
- pc (Programm Counter) Zeigt auf die aktuelle Java-Instruktion.
- Java Stack
- Wird beim Erstellen des Threads angelegt.
- Spielt die gleiche Rolle wie bei konventionellen Sprachen, z.B. zur Aufnahme von lokalen Variablen,
Zwischenergebnissen, Aufruf von Methoden und für Returnwerte.
- Auf ihm werden die sog. Frames gespeichert.
- Frames
- Für jede aufgerufene Methode wird ein sog. Frame auf dem Java-Stack des entsp. Thread angelegt.
- Er beinhaltet die lokalen Variablen und den Operanden-Stack.
Die lokalen Variablen werden in Java als Array angesprochen.
- Er dient zum speichern von Daten und Zwischenergebnissen wie z.B. Aufrufparameter oder Rückgabewerten.
- Nur ein Frame ist zur Zeit aktiv. Dieser wird als aktueller Frame bezeichnet und gehört zur aktuell ausgeführten Methode.
- Nach Beenden der Methode wird der Frame wieder gelöscht und der vorherige Frame wird aktueller Frame.
Referenzierung von Methoden
int add12and13() {
return addTwo(12,13);
}
Method int add12and13()
aload_0 // Lädt lokale Variable 0 (this) auf den Stack
bipush 12 // lädt int 12 auf den Stack
bipush 13 // lädt int 13 auf den Stack
invokevirtual #4 // Ruft die Methode Example.addTwo(II)I
ireturn // Gib int vom Stack als Returnwert zurück
Es wird der 4. Eintrag im Constant Pool referenziert der vom Typ CONSTANT_Methodref (10) sein muß.
Der Klassenname in Java muß den voll qualifizierten Klassenpfad enthalten.
Beispiel für Klassenpfad: java/lang/String
Anhand dieser Daten wird der Code der Methode in der Code-Tabelle gefunden und ausgeführt.
Referenzierung von Konstanten
Konstanten werden mit Hilfe von ldc geladen.
int i = 100000;
ldc #1 //lädt Konstantenwert auf Stack
Der Eintrag im Contant Pool muß vom Typ CONSTANT_Integer sein.
Referenzierung von Variablen
Instanzvariablen: getfield und putfield.
Klassenvariablen: getstatic und putstatic.
.
int i;
.
.
void setIt(int value){
i = value;
}
Method void setIt(int)
aload_0 // lädt this auf den Stack
iload_1 // lädt 1. Parameter auf den Stack
putfield #4 // ruft putfield mit Referenz auf Eintrag im Constant Pool
ireturn // gibt int zurück
Anhand dieser Daten und this
wird der Pointer auf die Variable in der Feldtabelle gefunden.
... [ Seminar WWW und JAVA ] ... [ Thema Java Virtual Machine ] ... [ Linkertechniken ] ... [ Compilertechniken ] ...