JavaRush /Java-Blog /Random-DE /Kompilieren und Ausführen von Java-Anwendungen unter der ...
Павел Голов
Level 34
Москва

Kompilieren und Ausführen von Java-Anwendungen unter der Haube

Veröffentlicht in der Gruppe Random-DE

Inhalt:

  1. Einführung
  2. Kompilieren in Bytecode
  3. Beispiel für die Programmkompilierung und -ausführung
  4. Ausführen eines Programms auf einer virtuellen Maschine
  5. Just-in-Time-Kompilierung (JIT).
  6. Abschluss
Kompilieren und Ausführen von Java-Anwendungen unter der Haube – 1

1. Einleitung

Hallo zusammen! Heute möchte ich mein Wissen darüber teilen, was unter der Haube der JVM (Java Virtual Machine) passiert, nachdem wir eine in Java geschriebene Anwendung ausgeführt haben. Heutzutage gibt es trendige Entwicklungsumgebungen, die Ihnen helfen, nicht über die Interna der JVM, das Kompilieren und Ausführen von Java-Code nachzudenken, was dazu führen kann, dass neue Entwickler diese wichtigen Aspekte übersehen. Gleichzeitig werden in Interviews häufig Fragen zu diesem Thema gestellt, weshalb ich mich entschieden habe, einen Artikel zu schreiben.

2. Kompilierung zum Bytecode

Unter der Haube Java-Anwendungen kompilieren und ausführen - 2
Beginnen wir mit der Theorie. Wenn wir eine Anwendung schreiben, erstellen wir eine Datei mit einer Erweiterung .javaund fügen darin Code in der Programmiersprache Java ein. Eine solche Datei, die für Menschen lesbaren Code enthält, wird als Quellcodedatei bezeichnet . Sobald die Quellcodedatei fertig ist, müssen Sie sie ausführen! Aber zum jetzigen Zeitpunkt enthält es Informationen, die nur für Menschen verständlich sind. Java ist eine plattformübergreifende Programmiersprache. Das bedeutet, dass in Java geschriebene Programme auf jeder Plattform ausgeführt werden können, auf der ein dediziertes Java-Laufzeitsystem installiert ist. Dieses System wird Java Virtual Machine (JVM) genannt. Um ein Programm vom Quellcode in Code zu übersetzen, den die JVM verstehen kann, müssen Sie es kompilieren. Der von der JVM verstandene Code wird Bytecode genannt und enthält eine Reihe von Anweisungen, die die virtuelle Maschine anschließend ausführt. Um Quellcode in Bytecode zu kompilieren, ist javacim JDK (Java Development Kit) ein Compiler enthalten. Als Eingabe akzeptiert der Compiler eine Datei mit der Erweiterung .java, die den Quellcode des Programms enthält, und als Ausgabe erzeugt er eine Datei mit der Erweiterung .class, die den für die Ausführung des Programms durch die virtuelle Maschine erforderlichen Bytecode enthält. Sobald ein Programm in Bytecode kompiliert wurde, kann es mithilfe einer virtuellen Maschine ausgeführt werden.

3. Beispiel für die Programmkompilierung und -ausführung

Angenommen, wir haben ein einfaches Programm, das in einer Datei enthalten ist Calculator.javaund zwei numerische Befehlszeilenargumente akzeptiert und das Ergebnis ihrer Addition ausgibt:
class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Um dieses Programm in Bytecode zu kompilieren, verwenden wir den Compiler javacin der Befehlszeile:
javac Calculator.java
Nach der Kompilierung erhalten wir als Ausgabe eine Datei mit Bytecode Calculator.class, die wir mit der auf unserem Computer installierten Java-Maschine über den Java-Befehl in der Befehlszeile ausführen können:
java Calculator 1 2
Beachten Sie, dass nach dem Dateinamen zwei Befehlszeilenargumente angegeben wurden – die Nummern 1 und 2. Nach der Ausführung des Programms wird in der Befehlszeile die Nummer 3 angezeigt. Im obigen Beispiel hatten wir eine einfache Klasse, die für sich allein lebt . Aber was ist, wenn die Klasse in einem Paket enthalten ist? Lassen Sie uns die folgende Situation simulieren: Erstellen Sie Verzeichnisse src/ru/javarushund platzieren Sie unsere Klasse dort. Jetzt sieht es so aus (wir haben den Paketnamen am Anfang der Datei hinzugefügt):
package ru.javarush;

class Calculator {
    public static void main(String[] args){
        int a = Integer.valueOf(args[0]);
        int b = Integer.valueOf(args[1]);

        System.out.println(a + b);
    }
}
Kompilieren wir eine solche Klasse mit dem folgenden Befehl:
javac -d bin src/ru/javarush/Calculator.java
In diesem Beispiel haben wir eine zusätzliche Compileroption verwendet , die die kompilierten Dateien in ein Verzeichnis mit einer ähnlichen Struktur wie das Verzeichnis -d binlegt , das Verzeichnis muss jedoch im Voraus erstellt werden. Diese Technik wird verwendet, um eine Verwechslung von Quellcodedateien mit Bytecodedateien zu vermeiden. Bevor Sie das kompilierte Programm ausführen, sollten Sie das Konzept erläutern . ist der Pfad, relativ zu dem die virtuelle Maschine nach Paketen und kompilierten Klassen sucht. Das heißt, auf diese Weise teilen wir der virtuellen Maschine mit, welche Verzeichnisse im Dateisystem die Wurzel der Java-Pakethierarchie sind. kann beim Programmstart über das Flag angegeben werden . Wir starten das Programm mit dem Befehl: binsrcbinclasspathClasspathClasspath-classpath
java -classpath ./bin ru.javarush.Calculator 1 2
In diesem Beispiel benötigten wir den vollständigen Namen der Klasse, einschließlich des Namens des Pakets, in dem sie sich befindet. Der endgültige Dateibaum sieht folgendermaßen aus:
├── src
│     └── ru
│          └── javarush
│                  └── Calculator.java
└── bin
      └── ru
           └── javarush
                   └── Calculator.class

4. Ausführung des Programms durch eine virtuelle Maschine

Also haben wir das schriftliche Programm gestartet. Aber was passiert, wenn ein kompiliertes Programm von einer virtuellen Maschine gestartet wird? Lassen Sie uns zunächst herausfinden, was die Konzepte Kompilierung und Codeinterpretation bedeuten. Kompilierung ist die Übersetzung eines in einer höheren Quellsprache geschriebenen Programms in ein entsprechendes Programm in einer niedrigen Sprache, die dem Maschinencode ähnelt. Bei der Interpretation handelt es sich um eine Operator-für-Anweisung (Befehl-für-Zeile, Zeile-für-Zeile) Analyse, Verarbeitung und sofortige Ausführung des Quellprogramms oder der Quellanforderung (im Gegensatz zur Kompilierung, bei der das Programm übersetzt wird, ohne es auszuführen). Die Java-Sprache verfügt sowohl über einen Compiler ( javac) als auch über einen Interpreter, bei dem es sich um eine virtuelle Maschine handelt, die Bytecode Zeile für Zeile in Maschinencode umwandelt und diesen sofort ausführt. Wenn wir also ein kompiliertes Programm ausführen, beginnt die virtuelle Maschine mit der Interpretation, d. h. der zeilenweisen Umwandlung von Bytecode in Maschinencode sowie seiner Ausführung. Leider ist die reine Bytecode-Interpretation ein ziemlich langer Prozess und macht Java im Vergleich zu seinen Konkurrenten langsam. Um dies zu vermeiden, wurde ein Mechanismus eingeführt, der die Interpretation des Bytecodes durch die virtuelle Maschine beschleunigt. Dieser Mechanismus wird als Just-in-Time-Kompilierung (JITC) bezeichnet.

5. Just-in-Time-Kompilierung (JIT).

Vereinfacht ausgedrückt ist der Mechanismus der Just-In-Time-Kompilierung folgender: Wenn Teile des Codes im Programm viele Male ausgeführt werden, können sie einmal in Maschinencode kompiliert werden, um ihre Ausführung in Zukunft zu beschleunigen. Nachdem ein solcher Teil des Programms in Maschinencode kompiliert wurde, führt die virtuelle Maschine bei jedem weiteren Aufruf dieses Teils des Programms den kompilierten Maschinencode sofort aus, anstatt ihn zu interpretieren, was die Ausführung des Programms natürlich beschleunigt. Die Beschleunigung des Programms wird durch einen erhöhten Speicherverbrauch (wir müssen den kompilierten Maschinencode irgendwo speichern!) und durch einen längeren Zeitaufwand für die Kompilierung während der Programmausführung erreicht. Die JIT-Kompilierung ist ein ziemlich komplexer Mechanismus, also lassen Sie uns einen Blick darauf werfen. Es gibt 4 Ebenen der JIT-Kompilierung von Bytecode in Maschinencode. Je höher die Kompilierungsstufe, desto komplexer ist sie, aber gleichzeitig ist die Ausführung eines solchen Abschnitts schneller als die eines Abschnitts mit einer niedrigeren Stufe. JIT – Der Compiler entscheidet, welche Kompilierungsstufe für jedes Programmfragment festgelegt werden soll, basierend darauf, wie oft dieses Fragment ausgeführt wird. Unter der Haube verwendet die JVM zwei JIT-Compiler – C1 und C2. Der C1-Compiler wird auch Client-Compiler genannt und ist nur in der Lage, Code bis zur 3. Ebene zu kompilieren. Der C2-Compiler ist für die 4., komplexeste und schnellste Kompilierungsebene verantwortlich.
Unter der Haube Java-Anwendungen kompilieren und ausführen - 3
Aus dem oben Gesagten können wir schließen, dass es für einfache Client-Anwendungen rentabler ist, den C1-Compiler zu verwenden, da es in diesem Fall für uns wichtig ist, wie schnell die Anwendung startet. Bei serverseitigen, langlebigen Anwendungen kann der Start zwar länger dauern, in Zukunft müssen sie aber schnell funktionieren und ihre Funktion erfüllen – hier eignet sich für uns der C2-Compiler. Wenn wir ein Java-Programm auf der x32-Version der JVM ausführen, können wir mithilfe der Flags -clientund manuell angeben, welchen Modus wir verwenden möchten -server. Wenn dieses Flag angegeben ist, -clientführt die JVM keine komplexen Bytecode-Optimierungen durch, was die Startzeit der Anwendung beschleunigt und den Speicherverbrauch reduziert. Bei Angabe des Flags -serverdauert der Start der Anwendung aufgrund komplexer Bytecode-Optimierungen länger und benötigt mehr Speicher zum Speichern von Maschinencode, das Programm wird jedoch in Zukunft schneller ausgeführt. In der x64-Version der JVM -clientwird das Flag ignoriert und standardmäßig die Anwendungsserverkonfiguration verwendet.

6. Fazit

Damit ist mein kurzer Überblick über die Funktionsweise des Kompilierens und Ausführens einer Java-Anwendung abgeschlossen. Hauptpunkte:
  1. Der Javac- Compiler wandelt den Quellcode eines Programms in Bytecode um, der auf jeder Plattform ausgeführt werden kann, auf der die Java Virtual Machine installiert ist.
  2. Nach der Kompilierung interpretiert die JVM den resultierenden Bytecode;
  3. Um Java-Anwendungen zu beschleunigen, nutzt die JVM einen Just-In-Time-Kompilierungsmechanismus, der die am häufigsten ausgeführten Abschnitte eines Programms in Maschinencode umwandelt und im Speicher speichert.
Ich hoffe, dieser Artikel hat Ihnen geholfen, ein tieferes Verständnis dafür zu erlangen, wie unsere Lieblingsprogrammiersprache funktioniert. Danke fürs Lesen, Kritik ist willkommen!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION