Seminarthemen→ Die Architektur der Java VM→ I. Virtuelle Maschinen • II.• III.• IV.• V.• VI.• VII.
Virtuelle Maschinen (kurz VM) sind keinesfalls ein besonders neue Technik. Bereits kurz nach dem Aufkommen der Hochsprachen gab es Ende der 60er Jahren mit dem O-Code (Objekt-Code) einen Zwischencode, der den eigentlichen Compiler maschinenunabhängig machte, und bereits interpretiert oder direkt weiter in maschinenabhängigen Code umgewandelt werden konnte. Allerdings war auch in den späten 70er Jahren die Rechenleistung der PC-Systeme meist noch zu gering, als dass die zusätzliche Indirektstufe durch einen Interpreter in Kauf genommen wurde. Später wurde der Zwischencode oder Bytecode
für den Endnutzer unsichtbar bei interpretierten Sprachen, wie Perl, Tcl oder Prolog als ein Zwischenschritt des Maschinencodeerzeugungsvorgangs eingesetzt. Mit der steigenden Programmkomplexität suchte man vermehrt nach Entwicklungstechniken und einheitlichen Konzepten, um Programme effizienter und fehlerfreier entwickeln zu können. Neben den Entwurfsmustern des Software-Designs entwickelt man in diesem Zusammenhang auch das Konzept des Interpreters zur virtuellen Maschiene weiter. Inzwischen war auch die Rechenleistung von PC-Systemen derartig angesteiegen, dass die Indirektstufe einer VM kein Problem mehr darstelle. Im Gegenteil: Dies wird immer mehr zu einem Vorteil gegenüber fertig compiliertem Maschinencode. Da eine VM den Maschinencode bei jedem Programmlauf neu erzeugt, kann sie den Instruktionsatz des physikalischen Prozessors optimal nutzen, und wesentlich mehr Werte auf Konstanten (statt Variablen) abbilden, die für eine richtige Sprungvorhersage unproblematisch sind, was bei PC-Prozessoren einen beachtlichen Teil der Performance ausmacht. Dies ist ein Beispiel für die bei virtuellen Maschinen mögliche Laufzeitoptimierung, welche immer wieder mit neuen Konzepten weiterentwicklt werden kann, ohne dass bestehende Bytecode
-Programme neu erzeugt werden müssten.
Das Schaubild in Abb. 1 zweigt nocheinmal den wesentlichen Unterschied zwischen der klassischen Maschienencodeerzeugung (in einem zusammenhängenden Gesammtprozess) und der Erzeugung des Bytecode
s einer virtuellen Maschine.
klassisch Copilierung | virtualisierte Copilierung |
---|---|
Quellcode |
Quellcode |
Lexikalische Analyse |
Lexikalische Analyse |
Syntax-Analyse |
Syntax-Analyse |
Semantische Analyse |
Semantische Analyse |
Zwischensprachen Generator |
Zwischensprachen Generator |
Zwischencode prinzipiel beliebiges Format |
Bytecode Format für VM spezifiziert |
Code-Optimierer |
|
Maschinencode-Generator |
|
Maschinencode evt. BS anh. immer HW abh. |
Entscheidend ist der maschinen- und betriebssystemunabhängige Zwischencode einer virtuellen Maschine. Dieser als Bytecode
bezeichnete Zwischencode ist immer genau spezifiziert, um auf allen zugehörigen VMs ablaufen zu können. Diese interpretieren diesen zur Laufzeit auf dem Zielsystem und wandeln ihn in äquivalenten nativen Maschienencode um. So können Programme in Form von Bytecode
auf jeder Architektur ausgeführt werden, auf der ihre virtuelle Maschine implementiert wurde.
Der wesentliche Unterscheid eines Bytecode
s zu einem herkömmlichen Zwischencode liegt in der Zielsetzung und dem Aufbau seines Befehlssatzes. Für herkömmlichen Zwischencode war vorallem entscheidend, dass...
Der Aufbau eines Bytecode
s setzt zudem vorraus, dass...
Da beinahe alle heute eingesetzten Prozessoren als kleinste Einheit den Typ byte
verwenden, nutzen die meisten virtuellen Maschienen diesen auch als grundlegende Einheit ihres Zwischencodes, was zu der Bezeichnung Bytecode
führte.
Durch die zusätzlich kodierten Information eines Bytecode
s kann dieser gewöhnlich auch wieder decompiliert werden. D.h. der Quellcode kann durch einen Decompiler zurückgewonnen werden. Allerdings entspricht der so erzeugte Quellcode dem Orginal nur funktionell. Dennoch ist die Ähnlichkeit zwischen Orginal und generiertem Quellcode inzwischen verblüffend groß. Dies kann natürlich auch genutzt werden, um den Bytecode
in eine andere Programmiersprache zu übersetzen, für die ein entsprechender Decompiler existiert.
Die Bezeichnung virtuelle Maschine entstammt dem, einem tatsächlichen Prozessor ähnlichen, generellen Aufbau eines VM-Interpreters. Aber auch Komponenten klassicher Interpreter sind enthalten. Von diesen unterscheidet sich eine VM vorallem durch
Bytecode Anwendung 1 |
Bytecode Anwendung n |
Code- Optimierer | Speicher- Organisation | Thread- Organisation | IO- Organisation |
Betriebssystem 1 |
Betriebssystem n |
Hardware A |
Hardware X |
Der Bytecode
wird zunächst vom Code-Optimierer allgemein oder Maschinenbezogen optimiert. Der so entstandene Code wird meist noch verifiziert, also auf Fehlerfreiheit und Sicherheit überprüft. Dies ist jedoch prinzipiel für die Funktion nicht entscheidend. Wird der Bytecode
als gültig angesehen, wird er über die in Software nachgebildeten Register oder einen Stack ausgeführt. Die einzelnen Insruktionen beziehen sich dabei ausschließlich auf virtuelle Register oder den Stack. Diese können zwar tatsächlichen Registern der Maschine entsprechen, dies ist jedoch der Implementierung der virtuellen Maschine überlassen. Auch der von einem Programm benötigte Speicher wird von der virtuellen Maschine organisiert, welche diesen in den allermeisten Fällen natürlich auf realen Speicher abbildet. Ein großer Vorteil ist dabei die automatische Speicherverwaltung, die sich um das Freigeben nicht mehr benötigter Speicherbereiche kümmert. Wie dies ermittelt wird, ist ebenso der Implementierung der virtuellen Maschine überlassen. Auch die Organisation der Threads obliegt der VM. Sie kann diese auf Threads eines darunterliegenden Betriebssystems abbilden, oder selbst als einziger Thread auf BS-Ebene oder direkt auf der Zielhardware laufen, und die Organisation der virtuellen Threads selbst übernehmen.
Der Einsatz von Interpretern oder virtuellen Maschinen macht die Maschinencodeerzeugung im ersten Moment komplexer/aufwendiger als die bisher übliche direkte Generierung des Maschinencodes (vergl. Abb. 1). Dieser "Nachteil" kehrt sich jedoch mit der Zeit in einen Vorteil um, da der erhöhte Aufwand nur einmal aufgewendet werden muss, die VM dann aber dauerhaft eine Reihe von Vorteilen mitbringt, was auch das Ziele einer Virtualisierung ist. Auch die zunehmende Komplexität von Anwendungsprogrammen erfordert eine möglichst hohen Wiederverwendbarkeit von Software. Virtuelle Maschienen bieten hier eine abstarkte wiederverwendbare Ebene, die den Entwicklungaufwand von Anwendungssoftware verringert. Dies folgt ehr indirekt aus den Virtualisierungszielen:
Bytecode
einer VM muss ohne Modifikationen auf allen Prozessorarchitekturen ausführbar sein, für welche eine virtuelle Maschine existiert. Daher sollte die VM selbst so aufgebaut sein, dass sie verbreiteten Architekturen zwar ähnelt, aber dennoch portabel ist, sodass ihre Instruktionen leicht auf viele Maschienensprachen übertragen werden können. Ein geschickt gewählter Bytecode
kann erheblich dazu beitragen.Bytecode
-Format eine wichtiger Faktor für die Ausführungsgeschwindigkeit, da mit dessen Laufzeitoptimierung spürbare Geschwindigkeitsgewinne erzeihlt werden können. Entscheidend ist dabei, dass sich der Kontrollfluss anhand des Bytecode
algorithmisch und/oder statistisch auswerten lässt.
Bytecode
einer VM. Dieser sollte möglichst kompakt sein, daBytecode
, erleichtert auch dessen Manipulation. Eine VM sollte daher Sicherungskonzepte enthalten, welcheBytecode
differenzieren können (wenigstens in sicher/unsicher). Bytecode
bereits vor der Ausführung erkennen, und soBytecode
ist, im Vergleich zu herkömlichen Maschienencode, jedoch niemals ein Nachteil von virtuellen Maschinen. Er ermöglicht lediglich die Bewertung und Kontrolle des ausgeführten Programms durch die VM, was diese auch nutzen sollte. Normaler Maschienencode kann heute so gut wie garnicht bewertet werden, und ist somit immer unsicherer und potentiell fähig, ein System in jeder erdenklichen Weise zu schädigen.
Bytecode
Bytecode
der VM übersetzt werden.Bytecode
Bytecode
der VM aufgerufen und so ausgeführt werden. Allerdings stellt diese Möglichkeit ein großes Problem für die Sicherheit dar, da der aufgerufene Code kaum verifiziert werden kann. Daher ist eine Unterstützung oft auch eine Grandwanderung zwischen Sicherheit und den Möglichkeiten der Interoperabilität.
Das letzte Ziel hat deutlich gemacht, dass die Ziele einer Virtualisierung auch miteinander konkurieren. Daher muss beim Entwurf einer virtuellen Maschine und des dazugehörigen Bytecode
s immer auch ein an das Einsatzgebiet der VM angepasster Schwerpunkt festgelegt werden. Die Entwicklung der Datenverarbeitungssysteme hat hier deutlich gezeigt, dass die Sicherheit eines System und die damit verbundene Überprüfbarkeit der Programme ein besonders wichtigen Platz einnehmen sollten, auch wenn dies zu Lasten der übrigen Virtualisierungsziele geschieht.
Seminarthemen→ Die Architektur der Java VM→ I. Virtuelle Maschinen • II.• III.• IV.• V.• VI.• VII.