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. Gemäß 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 Klasse
Class
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.
- 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.
Arten von Java-Loadern
In Java gibt es drei Standardlader, von denen jeder eine Klasse von einem bestimmten Speicherort lädt:-
Bootstrap ist ein einfacher Loader, auch Primordial ClassLoader genannt.
lädt Standard-JDK-Klassen aus dem rt.jar-Archiv
-
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
-
System ClassLoader – Systemlader.
lädt Anwendungsklassen, die in der Umgebungsvariablen CLASSPATH definiert sind
Abstrakte Klasse ClassLoader
Jeder Lader, mit Ausnahme des Basisladers, ist ein Nachkomme der abstrakten Klassejava.lang.ClassLoader
. Beispielsweise ist die Implementierung des Erweiterungsladers die Klasse sun.misc.Launcher$ExtClassLoader
und 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.ClassLoader
kann 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 ClassLoader
kann 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.
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,Class
wird 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.
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 oderClassNotFoundException
stoßen NoClassDefFoundError
. Nun, oder zumindest grob verstehen, wo die Ursache des Problems liegt. Daher tritt eine Ausnahme ClassNotFoundException
auf, 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 NoClassDefFoundError
ist 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.
GO TO FULL VERSION