Tools zum Starten und Entwickeln von Java-Anwendungen, Kompilieren und Ausführen auf der JVM

Es ist kein Geheimnis, dass Java derzeit eine der beliebtesten Programmiersprachen der Welt ist. Das offizielle Erscheinungsdatum für Java ist der 23. Mai 1995.

Dieser Artikel widmet sich den Grundlagen der Grundlagen: Er beschreibt die Grundfunktionen der Sprache, die für Anfänger als "Javisten" nützlich sind, und erfahrene Java-Entwickler können ihr Wissen auffrischen.

* Der Artikel wurde auf der Grundlage eines Berichts von Eugene Freiman - Java-Entwickler von IntexSoft erstellt.
Der Artikel enthält Links zu externen Materialien .





1. JDK, JRE, JVM


Java Development Kit ist ein Java Application Development Kit. Es enthält Java Development Tools und die Java Runtime Environment ( JRE ).

Zu den Java-Entwicklungstools gehören etwa 40 verschiedene Tools: Javac (Compiler), Java (Application Launcher), Javap (Disassembler für Java-Klassendateien), JDB (Java-Debugger) usw.

Die JRE-Laufzeit ist ein Paket mit allem, was zum Ausführen eines kompilierten Java-Programms erforderlich ist. Enthält die virtuelle JVM- Maschine und die Java-Klassenbibliothek .

JVM ist ein Programm zur Ausführung von Bytecode. Der erste Vorteil der JVM ist das Prinzip „Einmal schreiben, überall ausführen“ . Dies bedeutet, dass eine in Java geschriebene Anwendung auf allen Plattformen gleich funktioniert. Dies ist ein großer Vorteil der JVM und von Java selbst.

Vor dem Aufkommen von Java wurden viele Computerprogramme für bestimmte Computersysteme geschrieben, und die manuelle Speicherverwaltung wurde als effizienter und vorhersehbarer bevorzugt. Seit der zweiten Hälfte der neunziger Jahre, nach dem Aufkommen von Java, ist die automatische Speicherverwaltung eine gängige Praxis geworden.

Es gibt viele kommerzielle und Open Source-JVM-Implementierungen. Eines der Ziele bei der Erstellung neuer JVMs ist die Steigerung der Leistung für eine bestimmte Plattform. Jede JVM wird separat für die Plattform geschrieben, während es möglich ist, sie so zu schreiben, dass sie auf einer bestimmten Plattform schneller funktioniert. Die häufigste JVM-Implementierung ist der OpenJDK JVM-Hotspot. Es gibt auch Implementierungen von IBM J9 , Excelsior JET .

2. JVM-Code-Ausführung


Gemäß der Java SE-Spezifikation müssen Sie drei Schritte ausführen, damit Code in der JVM ausgeführt wird:

  • Laden des Bytecodes und Instanziieren der Klassenklasse
    Grob gesagt muss die Klasse geladen werden, um in die JVM zu gelangen. Hierfür gibt es separate Loader-Klassen, auf die wir etwas später zurückkommen werden.
  • Verknüpfen oder Verknüpfen
    Nach dem Laden der Klasse beginnt der Verknüpfungsprozess, bei dem der Bytecode analysiert und überprüft wird. Der Verknüpfungsprozess erfolgt wiederum in drei Schritten:

    - Überprüfung oder Überprüfung des Bytecodes: Die Richtigkeit der Anweisungen, die Möglichkeit eines Stapelüberlaufs in diesem Abschnitt des Codes und die Kompatibilität der Variablentypen werden überprüft. Die Prüfung erfolgt einmal für jede Klasse.
    - Vorbereitung oder Vorbereitung: In diesem Stadium wird gemäß der Spezifikation Speicher für statische Felder zugewiesen und ihre Initialisierung erfolgt;
    - Auflösung oder Auflösung: Auflösung symbolischer Links (wenn wir im Bytecode Dateien mit der Erweiterung .class öffnen, sehen wir numerische Werte anstelle von symbolischen Links).
  • Initialisieren des resultierenden Klassenobjekts
    In der letzten Phase wird die von uns erstellte Klasse initialisiert und von der JVM ausgeführt.

3. Klassenlader und ihre Hierarchie


Zurück zu Klassenladern, dies sind spezielle Klassen, die Teil der JVM sind. Sie laden Klassen in den Speicher und stellen sie zur Ausführung zur Verfügung. Loader arbeiten mit allen Klassen: sowohl mit unseren als auch mit denen, die direkt für Java benötigt werden.

Stellen Sie sich die Situation vor: Wir haben unsere Bewerbung geschrieben, und zusätzlich zu den Standardklassen gibt es unsere Klassen, und es gibt viele von ihnen. Wie wird die JVM damit arbeiten? Java implementiert das verzögerte Laden von Klassen, dh das verzögerte Laden. Dies bedeutet, dass das Laden der Klassen erst ausgeführt wird, wenn in der Anwendung kein Aufruf der Klasse erfolgt.

Hierarchie der Klassenlader





Der First Class Loader ist der Bootstrap Classloader . Es ist in C ++ geschrieben. Dies ist der Basislader , der alle Systemklassen aus dem Archiv rt.jar lädt . Gleichzeitig gibt es einen kleinen Unterschied zwischen dem Laden von Klassen aus rt.jar und unseren Klassen: Wenn die JVM Klassen aus rt.jar lädt, werden seitdem nicht alle Überprüfungsschritte ausgeführt, die beim Laden einer anderen Klassendatei ausgeführt werden Der JVM ist zunächst bekannt, dass alle diese Klassen bereits validiert sind. Daher sollten Sie keine Ihrer Dateien in dieses Archiv aufnehmen.

Der nächste Bootloader ist der Extension Classloader. Es lädt Erweiterungsklassen aus dem Ordner jre / lib / ext . Angenommen, Sie möchten, dass eine Klasse bei jedem Start des Java-Computers geladen wird. Dazu können Sie die Quellklassendatei in diesen Ordner kopieren und sie wird automatisch geladen.

Ein weiterer Bootloader ist der System Classloader . Es lädt die Klassen aus dem Klassenpfad, den wir beim Start der Anwendung angegeben haben.

Das Laden von Klassen erfolgt in einer Hierarchie:

  • Zunächst fordern wir eine Suche im System Class Loader-Cache an (der System Loader-Cache enthält Klassen, die bereits von ihm geladen wurden).
  • Wenn die Klasse nicht im Cache des Systemladeprogramms gefunden wurde, sehen wir uns den Klassenladeprogramm für die Cache-Erweiterung an.
  • Wenn die Klasse nicht im Erweiterungslader-Cache gefunden wird, wird die Klasse vom Bootstrap-Lader angefordert.

Wenn die Klasse nicht im Bootstrap-Cache gefunden wird, wird versucht, diese Klasse zu laden. Wenn Bootstrap die Klasse nicht laden konnte, delegiert es das Laden der Klasse an den Erweiterungslader. Wenn zu diesem Zeitpunkt die Klasse geladen wird, verbleibt sie im Cache des Extension-Klassenladeprogramms, und das Laden der Klasse ist abgeschlossen.

4. Struktur der Klassendatei und Startvorgang


Wir gehen direkt zur Struktur der Klassendateien über.

Eine in Java geschriebene Klasse wird mit der Erweiterung .class in eine einzelne Datei kompiliert. Wenn unsere Java-Datei mehrere Klassen enthält, kann eine Java-Datei mit der Erweiterung .class - Bytecode-Dateien dieser Klassen in mehrere Dateien kompiliert werden.

Alle Zahlen, Zeichenfolgen, Zeiger auf Klassen, Felder und Methoden werden im Konstantenpool gespeichert - dem Meta-Space- Speicherbereich. Die Klassenbeschreibung wird an derselben Stelle gespeichert und enthält den Namen, die Modifikatoren, die Superklasse, die Superschnittstellen, Felder, Methoden und Attribute. Attribute können wiederum zusätzliche Informationen enthalten.

Also beim Laden von Klassen:

  • Lesen der Klassendatei, d. h. Formatvalidierung
  • Die Klassendarstellung wird im konstanten Pool (Metaraum) erstellt.
  • Superklassen und Superschnittstellen werden geladen; Wenn sie nicht geladen sind, wird die Klasse selbst nicht geladen

5. Bytecode-Ausführung auf der JVM


Um Bytecode auszuführen, kann die JVM ihn zunächst interpretieren . Die Interpretation ist ein ziemlich langsamer Prozess. Während des Interpretationsprozesses „läuft“ der Interpreter Zeile für Zeile durch die Klassendatei und übersetzt sie in Befehle, die für die JVM verständlich sind.

Die JVM kann es auch senden , d.h. Kompilieren Sie in Maschinencode, der direkt auf der CPU ausgeführt wird.

Befehle, die häufig ausgeführt werden, werden nicht interpretiert, sondern sofort gesendet.

6. Zusammenstellung


Ein Compiler ist ein Programm, das die Quellteile von Programmen, die in einer höheren Programmiersprache geschrieben sind, in ein Maschinensprachenprogramm konvertiert, das für einen Computer „verständlich“ ist.

Compiler sind unterteilt in:

  • Nicht optimieren
  • Einfache Optimierung (Hotspot-Client): Arbeiten Sie schnell, generieren Sie jedoch nicht optimalen Code
  • Komplexe Optimierung (Hotspot-Server): Führen Sie komplexe Optimierungstransformationen durch, bevor Sie Bytecode generieren


Compiler können auch nach Kompilierungszeit klassifiziert werden:

  • Dynamische Compiler
    Sie arbeiten gleichzeitig mit dem Programm, was sich auf die Leistung auswirkt. Es ist wichtig, dass diese Compiler mit Code ausgeführt werden, der häufig ausgeführt wird. Während der Ausführung des Programms weiß die JVM, welcher Code am häufigsten ausgeführt wird, und um ihn nicht ständig zu interpretieren, übersetzt die virtuelle Maschine ihn sofort in Befehle, die bereits direkt auf dem Prozessor ausgeführt werden.
  • Statische Compiler
    Länger kompilieren, aber den optimalen Code für die Ausführung generieren. Von den Profis: Sie benötigen während der Programmausführung keine Ressourcen, jede Methode wird mithilfe von Optimierungen kompiliert.

7. Organisation des Speichers in Java


Ein Stack ist ein Speicherbereich in Java, der nach dem LIFO-Schema " Last In - Fisrt Out " oder " Last In, First Out " arbeitet.



Es wird benötigt, um Methoden zu speichern. Variablen auf dem Stapel sind vorhanden, solange die Methode ausgeführt wird, mit der sie erstellt wurden.

Wenn eine Methode in Java aufgerufen wird, wird ein Frame oder Speicherbereich auf dem Stapel erstellt und die Methode wird oben platziert. Wenn eine Methode die Ausführung abgeschlossen hat, wird sie aus dem Speicher entfernt, wodurch Speicher für die folgenden Methoden frei wird. Wenn der Stapelspeicher voll ist, löst Java eine Ausnahme von java.lang.StackOverFlowError aus . Dies kann beispielsweise passieren, wenn wir eine rekursive Funktion haben, die sich selbst aufruft und nicht genügend Speicher auf dem Stapel vorhanden ist.

Hauptmerkmale des Stapels:

  • Der Stapel wird gefüllt und freigegeben, wenn neue Methoden aufgerufen und abgeschlossen werden.
  • Der Zugriff auf diesen Speicherbereich ist schneller als der Heap.
  • Die Stapelgröße wird vom Betriebssystem bestimmt.
  • Es ist threadsicher, da jeder Stapel einen eigenen Stapel hat.

Ein weiterer Speicherbereich in Java ist Heap oder Heap . Es wird zum Speichern von Objekten und Klassen verwendet. Auf dem Heap werden immer neue Objekte erstellt und Verweise darauf auf dem Stapel gespeichert. Alle Objekte auf dem Heap haben globalen Zugriff, dh sie können von überall in der Anwendung aus aufgerufen werden.

Der Haufen ist in mehrere kleinere Teile unterteilt, die als Generationen bezeichnet werden:

  • Junge Generation - der Bereich, in dem sich kürzlich erstellte Objekte befinden
  • Alte (feste) Generation - der Bereich, in dem „langlebige“ Objekte aufbewahrt werden
  • Vor Java 8 gab es einen weiteren Bereich - die permanente Generierung -, der Metainformationen zu Klassen, Methoden und statischen Variablen enthält. Nach dem Aufkommen von Java 8 wurde beschlossen, diese Informationen separat außerhalb des Heaps zu speichern, und zwar im Meta-Bereich




Warum die ständige Generation verlassen? Dies ist zunächst auf einen Fehler zurückzuführen, der mit dem Überlauf des Bereichs verbunden war: Da Perm eine konstante Größe hatte und nicht dynamisch erweitert werden konnte, wurde früher oder später der Speicher leer, ein Fehler wurde ausgelöst und die Anwendung stürzte ab.

Der Meta-Space hat eine dynamische Größe und kann zur Laufzeit auf JVM-Speichergrößen erweitert werden.

Wichtige Heap-Funktionen:

  • Wenn dieser Speicherbereich voll ist, löst Java java.lang.OutOfMemoryError aus
  • Der Heap-Zugriff ist langsamer als der Stack-Zugriff
  • Garbage Collector sammelt nicht verwendete Objekte
  • Ein Heap ist im Gegensatz zu einem Stapel nicht threadsicher, da jeder Thread darauf zugreifen kann


Betrachten Sie anhand der obigen Informationen anhand eines einfachen Beispiels, wie die Speicherverwaltung durchgeführt wird:

public class App { public static void main(String[] args) { int id = 23; String pName = "Jon"; Person p = null; p = new Person(id, pName); } } class Person { int pid; String name; // constructors, getters/setters } 


Wir haben eine App-Klasse, in der die einzige Hauptmethode besteht aus:

- primitive ID- Variable vom Typ int mit dem Wert 23
- pName Referenzvariable vom Typ String mit dem Wert Jon
- Referenzvariable p vom Typ Person



Wie bereits erwähnt, wird beim Aufrufen einer Methode oben im Stapel ein Speicherbereich erstellt, in dem die für die Speicherung dieser Methode erforderlichen Daten gespeichert werden.
In unserem Fall ist dies ein Verweis auf die Personenklasse : Das Objekt selbst wird auf dem Heap gespeichert, und der Link wird auf dem Stapel gespeichert. Ein Link zur Zeichenfolge wird ebenfalls auf den Stapel verschoben, und die Zeichenfolge selbst wird auf dem Heap im Zeichenfolgenpool gespeichert. Das Grundelement wird direkt auf dem Stapel gespeichert.

Um den Konstruktor mit Person (String) -Parametern aus der main () -Methode auf dem Stapel aufzurufen, wird zusätzlich zum vorherigen main () -Aufruf ein separater Frame auf dem Stapel erstellt, in dem Folgendes gespeichert ist:

- this - Link zum aktuellen Objekt
- primitiver ID- Wert
- die Referenzvariable personName , die auf eine Zeichenfolge im Zeichenfolgenpool verweist.

Nachdem wir den Konstruktor aufgerufen haben, wird setPersonName () aufgerufen. Danach wird erneut ein neuer Frame auf dem Stapel erstellt, in dem dieselben Daten gespeichert werden: Objektreferenz, Zeilenreferenz , Variablenwert.

Wenn also die Setter- Methode ausgeführt wird, verschwindet der Rahmen, der Stapel wird gelöscht. Als nächstes wird der Konstruktor ausgeführt, der für den Konstruktor erstellte Frame wird gelöscht. Danach beendet die main () -Methode ihre Arbeit und wird ebenfalls vom Stapel entfernt.

Wenn andere Methoden aufgerufen werden, werden für sie im Kontext dieser spezifischen Methoden auch neue Frames erstellt.

8. Müllsammler


Garbage Collector arbeitet am Heap - einem Programm, das auf der virtuellen Java-Maschine ausgeführt wird und Objekte entfernt, auf die nicht zugegriffen werden kann.

Unterschiedliche JVMs können unterschiedliche Garbage Collection-Algorithmen haben, es gibt auch unterschiedliche Garbage Collectors.

Wir werden über den einfachsten Collector Serial GC sprechen. Wir fordern die Speicherbereinigung mit System.gc () an .



Wie oben erwähnt, ist der Haufen in zwei Bereiche unterteilt: Neue Generation und Alte Generation.

Die neue Generation (jüngere Generation) umfasst 3 Regionen: Eden , Survivor 0 und Survivor 1 .

Die alte Generation umfasst die Tenured- Region.

Was passiert, wenn wir ein Objekt in Java erstellen?

Zunächst fällt das Objekt in Eden . Wenn wir bereits viele Objekte erstellt haben und in Eden kein Platz mehr vorhanden ist, wird der Garbage Collector ausgelöst und Speicher freigegeben. Dies ist die sogenannte kleine Müllabfuhr - beim ersten Durchgang wird der Eden- Bereich gesäubert und die „überlebenden“ Objekte in die Survivor 0- Region gebracht. Damit ist die Region Eden vollständig befreit.

Wenn der Eden- Bereich wieder voll ist, arbeitet der Garbage Collector mit dem Eden- Bereich und Survivor 0 , der derzeit belegt ist. Nach der Reinigung fallen die überlebenden Objekte in eine andere Region - Überlebender 1 - und die anderen beiden bleiben sauber. Bei der anschließenden Speicherbereinigung wird Survivor 0 erneut als Zielregion ausgewählt. Deshalb ist es wichtig, dass eine der Überlebensregionen immer leer ist.

Die JVM überwacht Objekte, die ständig kopiert und von einer Region in eine andere verschoben werden. Und um diesen Mechanismus zu optimieren, verschiebt der Garbage Collector nach einem bestimmten Schwellenwert solche Objekte in den Tenured- Bereich.

Wenn in Tenured nicht genügend Platz für neue Objekte vorhanden ist, gibt es eine vollständige Speicherbereinigung - Mark-Sweep-Compact .



Während dieses Mechanismus wird bestimmt, welche Objekte nicht mehr verwendet werden, der Bereich wird von diesen Objekten gelöscht und der Tenured- Speicherbereich wird defragmentiert, d.h. nacheinander mit den notwendigen Objekten gefüllt.

Fazit


In diesem Artikel haben wir die grundlegenden Tools der Java-Sprache untersucht: JVM, JRE, JDK, das Prinzip und die Phasen der Ausführung, Kompilierung, Speicherorganisation des JVM-Codes sowie das Prinzip des Garbage Collector.

Source: https://habr.com/ru/post/de471772/


All Articles