Im Jahr 1995 führten Java und die Java Virtual Machine (JVM) zwei revolutionäre Konzepte ein, die seitdem integrale Bestandteile der modernen Softwareentwicklung sind, nämlich:
das "Write once, run anywhere"-Prinzip und
automatisches Speichermanagement.
Heute von so gut wie keinem Entwickler mehr hinterfragt, war Software-Interoperabilität Mitte der 1990er Jahre ein kühnes Konzept. Das wurde für eine ganze Generation von Developern prägend (Stichwort Garbage Collection - dazu später mehr). Für die Zukunft ist die Java Virtual Machine als essenzieller Bestandteil der Entwicklungslandschaft gesetzt.
Java Virtual Machine definiert
Die JVM ist ein Programm, dessen Zweck es ist, andere Programme auszuführen. Eine einfache Idee - und gleichzeitig ein Paradebeispiel für "Kung Fu Coding".
Sie ist eine virtuelle Maschine (VM), die Java-Class-Files portabel ausführt. Unabhängig davon, welches Betriebssystem oder welche Hardware tatsächlich vorhanden ist, schafft die JVM eine vorhersehbare Umgebung. Im Gegensatz zu einer echten virtuellen Maschine erstellt die JVM jedoch kein virtuelles Betriebssystem. Insofern wären die Bezeichnungen "Managed Runtime Environment" und "Process Virtual Machine" eigentlich treffender.
Die Java Virtual Machine erfüllt zwei wesentliche Funktionen:
Sie ermöglicht es, Java-Programme auf jedem Gerät oder Betriebssystem auszuführen ("write once, run anywhere"); und
managt und optimiert den Programmspeicher.
Bevor Java 1995 auf den Markt kam, wurden sämtliche Computerprogramme für ein spezifisches Betriebssystem geschrieben, Memory Management war eine Aufgabe der Softwareentwickler. Insofern waren Java und die JVM eine entlastende Offenbarung.
Zwar ist eine technische Definition der JVM nützlich, dabei sollte aber nicht die Perspektive der Developer unter den Tisch fallen.
Technische Definition: Die Java Virtual Machine ist die Spezifikation für ein Softwareprogramm, das Code ausführt und die Laufzeitumgebung für diesen bereitstellt.
Alltags-Definition: Die JVM ist die Art und Weise, wie wir unsere Java-Programme ausführen. Wir konfigurieren die Einstellungen und verlassen uns auf die Java Virtual Machine, wenn es darum geht, die Programmressourcen im laufenden Betrieb zu managen.
Wenn Entwickler von der JVM sprechen, meinen sie in der Regel den Prozess, der auf einem Rechner - insbesondere einem Server - läuft und die Ressourcennutzung für eine Java-Anwendung darstellt und kontrolliert. Im Gegensatz dazu steht die JVM-Spezifikation, diese beschreibt die Anforderungen für die Erstellung eines Programms, das diese Aufgaben erfüllt.
Die JVM war ursprünglich nur für Java gedacht - heute unterstützt sie allerdings diverse Skript- und Programmiersprachen, darunter Scala, Groovy und Kotlin. Diese Sprachen werden als JVM-Sprachen betrachtet - was bedeutet, dass auch Entwickler, die nicht in Java coden, weiterhin Zugang zum umfangreichen Ökosystem der Java-Bibliotheken haben.
Wer entwickelt und pflegt die Java Virtual Machine? |
Die JVM ist weit verbreitet, wird intensiv genutzt und von fähigen Programmierern, sowohl aus dem Enterprise- als auch Open-Source-Bereich gepflegt. Das OpenJDK-Projekt nahm mit der Entscheidung von Sun Microsoystems, Java in quelloffener Form zu veröffentlichen, seinen Anfang und wurde unter der Ägide von Oracle weitergeführt. |
Garbage Collection
Die häufigste Interaktion mit einer laufenden Java Virtual Machine findet im Rahmen der Überprüfung des Speicherverbrauchs in Heap und Stack Space statt. Mit Hilfe der Memory-Konfiguration der Java Virtual Machine können Leistungsoptimierungen erzielt werden.
Wie bereits erwähnt wurde vor Java der gesamte Programmspeicher vom Entwickler gemanagt. Diese Aufgabe übernimmt die JVM: Sie verwaltet den Speicher im Rahmen eines Prozesses namens Garbage Collection. Dieser identifiziert und beseitigt kontinuierlich ungenutzten Speicher in Java-Programmen.
In seiner Anfangszeit wurde Java stark kritisiert, weil es nicht so "close to the metal" wie C++ und daher nicht so schnell war. Der Garbage-Collection-Prozess war dabei besonders umstritten. Über die Jahre wurde deshalb eine Vielzahl von Algorithmen und Ansätzen für diesen Prozess vorgeschlagen und eingesetzt. Durch die konsequente Weiterentwicklung und Optimierung konnte die Garbage Collection letztlich erheblich verbessert werden und sich durchsetzen. Ebenso wie Automatic Memory Management, das sich im Zeitverlauf zum gängigen Merkmal moderner Programmiersprachen wie JavaScript und Python entwickelte.
Was bedeutet "close to the metal"? |
Wenn davon die Rede ist, dass eine Programmiersprache oder -Plattform "close to the metal" ist, ist gemeint, dass Entwickler in der Lage sind, den Speicher eines Betriebssystems programmatisch - also, indem sie Code schreiben - zu managen. Theoretisch können Developer ihren Programmen mehr Leistung abringen, indem sie festlegen, wie viel Speicher verwendet und wann dieser entsorgt werden soll. Das Memory Management an einen hochentwickelten Prozess zu delegieren, führt in den meisten Fällen zu einer besseren Performance und einer geringeren Fehlerquote. |
Die Komponenten der Java Virtual Machine
Man kann vertreten, dass die Java Virtual Machine im Wesentlichen aus drei Teilen besteht:
Spezifikation,
Implementierung, und
Instanz.
Im Folgenden betrachten wir diese Komponenten en detail.
JVM-Spezifikation
Zuallererst ist die JVM eine Softwarespezifikation. Die hebt in gewisser Weise zirkulär hervor, dass die Implementierungsdetails nicht in der Spezifikation definiert sind, um ein Maximum an Kreativität bei der Umsetzung zu ermöglichen: Um die Java Virtual Machine korrekt zu implementieren, müssen Sie nur dazu in der Lage sein, das Format des Class-Files zu lesen und die darin spezifizierten Operationen korrekt auszuführen.
Johann Sebastian Bach hat ein ähnliches Prinzip einmal beschrieben, als es darum ging, Musik zu komponieren:
Alles, was die JVM tun muss, ist also, Java-Programme korrekt auszuführen. Das klingt einfach und sieht von außen vielleicht sogar einfach aus, ist aber ein gewaltiges Unterfangen - insbesondere angesichts der Leistungsfähigkeit und Flexibilität von Java.
JVM-Implementierungen
Die JVM-Spezifikation zu implementieren, resultiert in einem tatsächlichen Softwareprogramm. Dabei gibt es viele verschiedene JVM-Implementierungen, sowohl quelloffene als auch proprietäre.
HotSpot von OpenJDK ist die JVM-Referenzimplementierung und nach wie vor eine der am gründlichsten getesteten Codebasen der Welt.
Eine weitere interessante und beliebte Implementierung ist GraalVM, die sich durch hohe Performance und Support für andere Nicht-JVM-Sprachen wie C++ und Rust (über die LLVM-Spezifikation) auszeichnet.
Es gibt auch domänenspezifische JVMs wie beispielweise leJOS für Embedded Robotics.
Normalerweise laden Sie die JVM herunter und installieren sie als Bestandteil einer Java-Laufzeitumgebung (JRE). Die JRE ist der auf der Festplatte befindliche Teil von Java, der eine laufende JVM erzeugt.
JVM-Instanz
Nachdem die JVM-Spezifikation implementiert und als Softwareprodukt freigegeben wurde, können Sie sie als Programm herunterladen und ausführen - eine Instanz (oder instanziierte Version) der Java Virtual Machine. Wenn Entwickler von der JVM sprechen, meinen sie in der Regel eine JVM-Instanz, die in einer Softwareentwicklungs- oder Produktionsumgebung läuft.
Was ist eine Softwarespezifikation? |
Eine Softwarespezifikation (oder Spec) ist ein Designdokument, das in für Menschen lesbarer Form beschreibt, wie ein Softwaresystem funktionieren soll. Der Zweck einer Spezifikation besteht darin, eine klare Beschreibung der Anfordeurngen zu erstellen, nach der sich die Software Engineers bei ihrer Arbeit richten können. |
Wie die JVM Class-Files lädt
Nachdem wir die Rolle der Java Virtual Machine bei der Ausführung von Java-Applikationen beleuchtet haben, geht es nun darum, wie sie diese Aufgabe erfüllt. Damit Java-Anwendungen ausgeführt werden können, ist die JVM auf den Java Class Loader und eine Java Execution Engine angewiesen.
Der Java Class Loader
Alles in Java ist eine Klasse und alle Java-Anwendungen basieren auf Klassen. Dabei kann eine Anwendung aus nur einer Klasse oder aus Tausenden bestehen. Um eine Java-App ausführen zu können, muss die JVM kompilierte Klassendateien in einen Kontext laden, zum Beispiel auf einen Server. Dazu ist sie auf den Class Loader angewiesen. Mit dem Befehl java classfile
starten Sie eine Java Virtual Machine und laden die entsprechende Klasse.
Der Java Class Loader ist der Bestandteil der JVM, der die Klassen in den Speicher lädt und zur Ausführung bereitstellt. Um diesen Vorgang so effizient wie möglich zu gestalten, kommen vergleichsweise simple Techniken wie Lazy Loading und Caching zum Einsatz. Das liegt daran, dass diese Aufgabe keine allzu großen Herausforderungen aufwirft - zumindest im Vergleich zu Aufgaben wie Portable Runtime Memory Management.
Jede JVM enthält einen Class Loader. Die JVM-Spezifikation beschreibt Standardmethoden, um diesen zur Laufzeit anzufragen und zu manipulieren, aber die JVM-Implementierung ist dafür verantwortlich, diese Fähigkeiten anzuwenden. Aus Entwicklerperspektive ist der zugrundeliegende Class-Loader-Mechanismus eine Blackbox.
Die Execution Engine
Sobald der Class Loader seine Arbeit erledigt hat, beginnt die JVM damit, den Code in jeder Klasse auszuführen. Diese Funktion übernimmt die Java Execution Engine (JEE) als (unerlässliche) Komponente der Java Virtual Machine.
Code auszuführen beinhaltet dabei auch, den Zugriff auf Systemressourcen zu managen: Die JEE steht zwischen dem laufenden Programm - mit seinen Anforderungen an Datei-, Netzwerk- und Speicherressourcen - und dem Betriebssystem, das diese Ressourcen bereitstellt.
Die Systemressourcen lassen sich in zwei große Kategorien einteilen: Speicher und alles andere. Erinnern Sie sich daran, dass die JVM für die Beseitigung von ungenutztem Speicher verantwortlich ist und dass die Garbage Collection der Mechanismus ist, der das vornimmt. Die JVM ist auch dafür verantwortlich, die Referenzstruktur zuzuweisen und zu pflegen, die Entwickler als selbstverständlich ansehen. Die JEE ist beispielsweise dafür verantwortlich, das Java-Keyword new
in eine betriebssystemspezifische Anforderung für die Speicherzuweisung umzuwandeln.
Neben dem Speicher verwaltet die Execution Engine auch die Ressourcen für den Zugriff auf das Dateisystem und die Netzwerk-E/A. Da die JVM betriebssystemübergreifend interoperabel ist, ist das keine leichte Aufgabe: Zusätzlich zu den Ressourcenanforderungen der einzelnen Anwendungen muss die JEE auf die jeweilige Betriebssystemumgebung reagieren. Auf diese Weise ist die JVM in der Lage, Anforderungen "in freier Wildbahn" zu bewältigen.
Die Evolution der Java Virtual Machine
Als bewährte Laufzeitumgebung mit standardisierter Konfiguration, Monitoring und Management eignet sich die Java Virtual Machine hervorragend für die containerisierte Entwicklung mit Docker oder Kubernetes. Darüber hinaus empfiehlt sie sich auch für Platform-as-a-Service und eine Vielzahl von Serverless-Ansätzen. All diese Faktoren machen die JVM auch für Microservices-Architekturen attraktiv.
Eine wichtige Funktion für die Zukunft zeichnet sich mit Project Loom ab, das virtuelle Threads in die JVM einführen will. Die sind in der Lage, Concurrency auf einer Abstraktionsebene zu ermöglichen, die über Betriebssystemprozesse hinausgeht. Virtuelle Threads sind zudem in der Lage, sich Speicher zu teilen, was zu enormen Verbesserungen - sowohl bei den Programmiersprachen selbst als auch hinsichtlich der allgemeinen Performance - führen könnte. (fm)
Dieser Beitrag basiert auf einem Artikel unserer US-Schwesterpublikation Infoworld.