JavaRush /Java-Blog /Random-DE /Wie Klassen in die JVM geladen werden
Aleksandr Zimin
Level 1
Санкт-Петербург

Wie Klassen in die JVM geladen werden

Veröffentlicht in der Gruppe Random-DE
Nachdem der schwierigste Teil der Arbeit eines Programmierers abgeschlossen und die Anwendung „Hello World 2.0“ geschrieben ist, muss nur noch das Distributionskit zusammengestellt und an den Kunden oder zumindest an den Testdienst übergeben werden. In der Distribution ist alles wie es sein soll und wenn wir unser Programm starten, kommt die Java Virtual Machine auf den Plan. Es ist kein Geheimnis, dass die virtuelle Maschine Befehle in Klassendateien in Form von Bytecode liest und sie als Anweisungen an den Prozessor übersetzt. Ich schlage vor, ein wenig über das Schema zu verstehen, wie Bytecode in die virtuelle Maschine gelangt.

Klassenlader

Es wird verwendet, um der JVM kompilierten Bytecode bereitzustellen, der normalerweise in Dateien mit der Erweiterung gespeichert wird .class, aber auch aus anderen Quellen bezogen werden kann, beispielsweise über das Netzwerk heruntergeladen oder von der Anwendung selbst generiert. Wie Klassen in die JVM geladen werden – 1Gemäß der Java SE-Spezifikation müssen Sie drei Schritte ausführen, um Code in der JVM zum Laufen zu bringen:
  • Laden von Bytecode aus Ressourcen und Erstellen einer Instanz der KlasseClass

    Dazu gehört die Suche nach der angeforderten Klasse unter den zuvor geladenen, das Erhalten von Bytecode zum Laden und Überprüfen seiner Richtigkeit, das Erstellen einer Instanz der Klasse Class(für die Arbeit damit zur Laufzeit) und das Laden von übergeordneten Klassen. Wenn übergeordnete Klassen und Schnittstellen nicht geladen wurden, gilt die betreffende Klasse als nicht geladen.

  • Bindung (oder Verlinkung)

    Laut Spezifikation ist diese Stufe in drei weitere Stufen unterteilt:

    • Bei der Verifizierung wird die Korrektheit des empfangenen Bytecodes überprüft.
    • Vorbereitung : Zuweisen von RAM für statische Felder und deren Initialisierung mit Standardwerten (in diesem Fall erfolgt die explizite Initialisierung, falls vorhanden, bereits in der Initialisierungsphase).
    • Auflösung , Auflösung symbolischer Verknüpfungen von Typen, Feldern und Methoden.
  • Initialisieren des empfangenen Objekts

    Hier scheint im Gegensatz zu den vorherigen Absätzen alles klar zu sein, was passieren soll. Es wäre natürlich interessant zu verstehen, wie das genau geschieht.

Alle diese Schritte werden nacheinander mit den folgenden Anforderungen ausgeführt:
  • Die Klasse muss vollständig geladen sein, bevor sie verknüpft werden kann.
  • Eine Klasse muss vor der Initialisierung vollständig getestet und vorbereitet werden.
  • Verbindungsauflösungsfehler treten während der Programmausführung auf, auch wenn sie bereits beim Verknüpfen erkannt wurden.
Wie Sie wissen, implementiert Java das verzögerte (oder verzögerte) Laden von Klassen. Dies bedeutet, dass das Laden von Klassen von Referenzfeldern der geladenen Klasse erst dann durchgeführt wird, wenn die Anwendung auf eine explizite Referenz auf sie stößt. Mit anderen Worten: Das Auflösen symbolischer Links ist optional und erfolgt nicht standardmäßig. Allerdings kann die JVM-Implementierung auch energetisches Klassenladen nutzen, d.h. Alle symbolischen Links müssen sofort berücksichtigt werden. Für diesen Punkt gilt die letzte Anforderung. Es ist auch erwähnenswert, dass die Auflösung symbolischer Links nicht an irgendeine Stufe des Klassenladens gebunden ist. Im Allgemeinen ist jede dieser Phasen eine gute Studie; versuchen wir, die erste herauszufinden, nämlich das Laden des Bytecodes.

Arten von Java-Loadern

In Java gibt es drei Standardlader, von denen jeder eine Klasse von einem bestimmten Speicherort lädt:
  1. Bootstrap ist ein einfacher Loader, auch Primordial ClassLoader genannt.

    lädt Standard-JDK-Klassen aus dem rt.jar-Archiv

  2. Extension ClassLoader – Erweiterungslader.

    lädt Erweiterungsklassen, die sich standardmäßig im Verzeichnis jre/lib/ext befinden, aber über die Systemeigenschaft java.ext.dirs festgelegt werden können

  3. System ClassLoader – Systemlader.

    lädt Anwendungsklassen, die in der Umgebungsvariablen CLASSPATH definiert sind

Java verwendet eine Hierarchie von Klassenladern, wobei der Stamm natürlich der Basislader ist. Als nächstes kommt der Erweiterungslader und dann der Systemlader. Natürlich speichert jeder Lader einen Zeiger auf den übergeordneten Lader, um das Laden an ihn delegieren zu können, falls er selbst dazu nicht in der Lage ist.

Abstrakte Klasse ClassLoader

Jeder Lader, mit Ausnahme des Basisladers, ist ein Nachkomme der abstrakten Klasse java.lang.ClassLoader. Beispielsweise ist die Implementierung des Erweiterungsladers die Klasse sun.misc.Launcher$ExtClassLoaderund der Systemlader ist sun.misc.Launcher$AppClassLoader. Der Basislader ist nativ und seine Implementierung ist in der JVM enthalten. Jede Klasse, die erweitert wird, java.lang.ClassLoaderkann ihre eigene Möglichkeit bieten, Klassen mit Blackjack und denselben Klassen zu laden. Dazu ist es notwendig, die entsprechenden Methoden neu zu definieren, was ich im Moment nur oberflächlich betrachten kann, weil Ich habe dieses Problem nicht im Detail verstanden. Hier sind sie:
package java.lang;
public abstract class ClassLoader {
    public Class<?> loadClass(String name);
    protected Class<?> loadClass(String name, boolean resolve);
    protected final Class<?> findLoadedClass(String name);
    public final ClassLoader getParent();
    protected Class<?> findClass(String name);
    protected final void resolveClass(Class<?> c);
}
loadClass(String name)Eine der wenigen öffentlichen Methoden, die den Einstiegspunkt zum Laden von Klassen darstellt. Seine Implementierung läuft darauf hinaus, eine andere geschützte Methode aufzurufen loadClass(String name, boolean resolve), die überschrieben werden muss. Wenn Sie sich die Javadoc dieser geschützten Methode ansehen, können Sie etwa Folgendes verstehen: Als Eingabe werden zwei Parameter bereitgestellt. Einer davon ist der Binärname der Klasse (oder der vollständig qualifizierte Klassenname), die geladen werden muss. Der Klassenname wird mit einer Liste aller Pakete angegeben. Der zweite Parameter ist ein Flag, das bestimmt, ob eine symbolische Linkauflösung erforderlich ist. Standardmäßig ist es false , was bedeutet, dass das verzögerte Laden von Klassen verwendet wird. Als nächstes gibt es laut Dokumentation in der Standardimplementierung der Methode einen Aufruf findLoadedClass(String name), der prüft, ob die Klasse bereits zuvor geladen wurde und wenn ja, einen Verweis auf diese Klasse zurückgibt. Andernfalls wird die Klassenlademethode des übergeordneten Laders aufgerufen. Wenn keiner der Lader eine geladene Klasse finden konnte, versucht jeder von ihnen in umgekehrter Reihenfolge, diese Klasse zu finden und zu laden und überschreibt dabei die findClass(String name). Dies wird im Kapitel „Klassenladeschema“ ausführlicher besprochen. Und zu guter Letzt wird nach dem Laden der Klasse je nach Resolve- Flag entschieden, ob Klassen über symbolische Links geladen werden sollen. Ein klares Beispiel ist, dass die Auflösungsphase während der Klassenladephase aufgerufen werden kann. Dementsprechend ClassLoaderkann der benutzerdefinierte Loader durch Erweitern der Klasse und Überschreiben ihrer Methoden seine eigene Logik für die Bereitstellung von Bytecode an die virtuelle Maschine implementieren. Java unterstützt auch das Konzept eines „aktuellen“ Klassenladers. Der aktuelle Loader ist derjenige, der die aktuell ausgeführte Klasse geladen hat. Jede Klasse weiß, mit welchem ​​Loader sie geladen wurde, und Sie können diese Informationen erhalten, indem Sie ihren aufrufen String.class.getClassLoader(). Für alle Anwendungsklassen ist der „aktuelle“ Loader normalerweise der Systemlader.

Drei Prinzipien des Klassenladens

  • Delegation

    Die Anforderung zum Laden der Klasse wird an den übergeordneten Lader weitergeleitet und es wird nur dann versucht, die Klasse selbst zu laden, wenn der übergeordnete Lader die Klasse nicht finden und laden konnte. Mit diesem Ansatz können Sie Klassen mit dem Ladeprogramm laden, das dem Basisladeprogramm möglichst nahe kommt. Dadurch wird eine maximale Sichtbarkeit der Klasse erreicht. Jeder Loader zeichnet die von ihm geladenen Klassen auf und legt sie in seinem Cache ab. Die Menge dieser Klassen wird als Geltungsbereich bezeichnet.

  • Sichtweite

    Der Lader sieht nur „seine“ Klassen und die Klassen des „Elternteils“ und hat keine Ahnung, welche Klassen von seinem „Kind“ geladen wurden.

  • Einzigartigkeit

    Eine Klasse kann nur einmal geladen werden. Der Delegationsmechanismus stellt sicher, dass der Loader, der das Laden der Klasse initiiert, eine Klasse, die zuvor in die JVM geladen wurde, nicht überlastet.

Daher sollte sich ein Entwickler beim Schreiben seines Bootloaders an diesen drei Prinzipien orientieren.

Klassenladeschema

Wenn ein Aufruf zum Laden einer Klasse erfolgt, wird diese Klasse im Cache bereits geladener Klassen des aktuellen Laders gesucht. Wenn die gewünschte Klasse noch nicht geladen wurde, übergibt das Prinzip der Delegation die Kontrolle an den übergeordneten Lader, der in der Hierarchie eine Ebene höher liegt. Der übergeordnete Loader versucht außerdem, die gewünschte Klasse in seinem Cache zu finden. Wenn die Klasse bereits geladen wurde und der Loader ihren Speicherort kennt, Classwird ein Objekt dieser Klasse zurückgegeben. Wenn nicht, wird die Suche fortgesetzt, bis sie den Basis-Bootloader erreicht. Wenn der Basislader keine Informationen über die erforderliche Klasse hat (d. h. sie wurde noch nicht geladen), wird der Bytecode dieser Klasse an der Stelle der Klassen gesucht, die dem angegebenen Lader bekannt sind, und wenn die Klasse dies nicht kann geladen wird, kehrt die Steuerung zum untergeordneten Ladeprogramm zurück, das versucht, aus ihm bekannten Quellen zu laden. Wie oben erwähnt, ist der Speicherort der Klassen für den Basislader die Bibliothek rt.jar, für den Erweiterungslader das Verzeichnis mit den Erweiterungen jre/lib/ext, für das System CLASSPATH und für den Benutzer kann es etwas anderes sein . Somit verläuft der Fortschritt beim Laden von Klassen in die entgegengesetzte Richtung – vom Root-Loader zum aktuellen. Wenn der Bytecode der Klasse gefunden wird, wird die Klasse in die JVM geladen und eine Instanz des Typs abgerufen Class. Wie Sie leicht erkennen können, ähnelt das beschriebene Ladeschema der obigen Implementierung der Methode loadClass(String name). Unten können Sie dieses Diagramm im Diagramm sehen.
Wie Klassen in die JVM geladen werden – 2

Als Schlussfolgerung

In den ersten Schritten des Erlernens einer Sprache ist es nicht unbedingt erforderlich, zu verstehen, wie Klassen in Java geladen werden, aber die Kenntnis dieser Grundprinzipien wird Ihnen helfen, nicht zu verzweifeln, wenn Sie auf Fehler wie oder ClassNotFoundExceptionstoßen NoClassDefFoundError. Nun, oder zumindest grob verstehen, wo die Ursache des Problems liegt. Daher tritt eine Ausnahme ClassNotFoundExceptionauf, wenn eine Klasse während der Programmausführung dynamisch geladen wird und Lader die erforderliche Klasse weder im Cache noch entlang des Klassenpfads finden können. Der Fehler NoClassDefFoundErrorist jedoch kritischer und tritt auf, wenn die erforderliche Klasse während der Kompilierung verfügbar war, während der Programmausführung jedoch nicht sichtbar war. Dies kann passieren, wenn das Programm vergessen hat, die verwendete Bibliothek einzubinden. Nun, allein die Tatsache, dass Sie die Prinzipien der Struktur des Werkzeugs verstehen, das Sie bei Ihrer Arbeit verwenden (nicht unbedingt ein klares und detailliertes Eintauchen in seine Tiefen), verleiht dem Verständnis der Prozesse, die innerhalb dieses Mechanismus ablaufen, eine gewisse Klarheit wiederum führt zu einem sicheren Umgang mit diesem Werkzeug.

Quellen

Funktionsweise von ClassLoader in Java Insgesamt eine sehr nützliche Quelle mit einer leicht zugänglichen Darstellung von Informationen. Laden von Klassen, ClassLoader Ein ziemlich langer Artikel, aber mit Schwerpunkt darauf, wie Sie mit denselben Klassen Ihre eigene Loader-Implementierung erstellen können. ClassLoader: dynamisches Laden von Klassen Leider ist diese Ressource derzeit nicht verfügbar, aber dort habe ich das verständlichste Diagramm mit einem Klassenladeschema gefunden, sodass ich nicht anders kann, als es hinzuzufügen. Java SE-Spezifikation: Kapitel 5. Laden, Verknüpfen und Initialisieren
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION