Klassenbeladungsprozess
Der Hauptauftrag eines Klassenladers besteht darin, Klassendateien in die JVM zu laden. Wie in der folgenden Abbildung gezeigt, ist der Prozess in drei Schritte unterteilt:
1. Laden: Suchen Sie die zu geladene Klassendatei und laden Sie den Byte -Stream in den JVM.
2. Link: Zeigen Sie die grundlegendste Speicherstruktur für das Laden aus, um ihre Informationen wie Eigenschaften, Methoden und referenzierte Klassen zu speichern. Zu diesem Zeitpunkt ist die Klasse immer noch nicht verfügbar;
(1) Überprüfung: Überprüfen Sie den geladenen Bytestrom wie Format und Sicherheit;
(2) Speicherzuweisung: Bereiten Sie den Speicherraum für diese Klasse vor, um ihre Eigenschaften, Methoden und referenzierten Klassen darzustellen;
(3) Analyse: Laden Sie andere Klassen, auf die in dieser Klasse verwiesen wird, z. B. übergeordnete Klasse, implementierte Schnittstellen usw.
3. Initialisierung: Weisen Sie den Klassenvariablen Werte zu.
Die Klassenladerebene
Die gepunktete Linie in der Abbildung unten sind mehrere wichtige Klassenlader, die von JDK bereitgestellt werden, und die detaillierte Beschreibung lautet wie folgt:
(1) Bootstrap -Klasse Loader: Wenn Sie eine Klasse mit der Hauptfunktion starten, laden Sie das JAR -Paket im Verzeichnis Java_Home/lib oder -xBootClassPath;
.
(3) Systemklassenlader: Klassenpfad oder -djava.Class.Path -Klasse oder JAR -Paket im angegebenen Verzeichnis laden.
Was Sie wissen müssen, ist:
1. Zusätzlich zum Bootstrap -Klassenloader sind andere Klassenlader Unterklassen der Java.lang.Classloader -Klasse;
2. Bootstrap -Klassenlader ist in Java nicht implementiert. Wenn Sie keinen personalisierten Klassenlader verwenden, ist java.lang.string.class.getClassloader () null und der übergeordnete Loader des Erweiterungsklassenladers ist ebenfalls null;
3.. Mehrere Möglichkeiten, Klassenlader zu erhalten:
(1) Bootstrap -Klassenlader erhalten: Was Sie erhalten, muss null sein, wenn Sie versuchen, den Lader der Bootstrap -Klasse zu erhalten. Sie können es folgendermaßen überprüfen: Verwenden Sie die GetClassloader -Methode von Klassenobjekten im RT.JAR -Paket wie Java.lang.String.class.getClassloader (), um den Lader der Erweiterungsklassen zu erhalten oder zu erhalten, und rufen dann die GetParent -Methode auf, um zu erhalten.
(2) den Lader der Erweiterungsklassen erhalten: Verwenden Sie die GetClassloader -Methode des Klassenobjekts im JAR -Paket unter dem Verzeichnis java_home/lib/ext oder erhalten Sie zuerst den Lader der Systemklasse und erhalten Sie ihn dann über seine GetParent -Methode.
(3) Systemklassenlader erhalten: Rufen Sie die GetClassloader -Methode des Klassenobjekts auf, das die Hauptfunktion enthält, oder rufen Sie Thread.CurrentThread ().
(4) Benutzerdefinierte Klassenlader erhalten: Rufen Sie die GetClassloader-Methode des Klassenobjekts an oder rufen Sie Thread.CurrentThread (). GetContextClassloader ();
Betriebsprinzipien des Klassenladers
1. Agentenprinzip
2. Sichtbarkeitsprinzip
3. Das Prinzip der Einzigartigkeit
V.
Warum den Proxy -Modus verwenden? Dies kann zunächst die doppelte Belastung einer Klasse verringern. (Gibt es einen anderen Grund?)
Wo zu missverstehen:
Im Allgemeinen wird angenommen, dass die Proxy -Reihenfolge des Klassenladers zuerst übergeordnet ist, dh:
1. Beim Laden einer Klasse prüft der Klassenlader zuerst, ob sie die Klasse geladen hat. Wenn es geladen wurde, wird es zurückkehren; Andernfalls fragen Sie den übergeordneten Lader zum Proxy.
2. Der übergeordnete Lader wiederholt den Betrieb von 1 bis der Lader der Bootstrap -Klasse;
3. Wenn der Bootstrap -Klasse -Loader die Klasse nicht lädt, wird versucht, sie zu laden, und wenn er erfolgreich ist, wird er zurückgegeben. Wenn es fehlschlägt, wird eine ClassNotFoundException geworfen, es wird vom Kinderlader geladen.
4. Die Unterklasse -Laderin fängt die Ausnahme ein und versucht sie zu laden. Wenn es erfolgreich ist, kehrt es zurück. Wenn es fehlschlägt, wirft es eine ClassNotFoundException aus, bis der geladene Unterklasselader initiiert wird.
Dieses Verständnis ist für Lader wie Bootstrap -Klassenlader, Ausweiterungsklassenlader und Systemklassenlader korrekt. Einige personalisierte Lader sind jedoch nicht der Fall. Zum Beispiel sind einige Klassenlader, die vom IBM Web Sphere Portal Server implementiert wurden, übergeordnet, was der untergeordnete Loader ist, der versucht, zuerst zu laden. Wenn die Last fehlschlägt, wird der übergeordnete Loader gefragt. Der Grund dafür ist: Wenn Sie erwarten, dass eine bestimmte Version von LOG4J von allen Anwendungen verwendet wird, setzen Sie sie in die WAS_HOME -Bibliothek und sie wird geladen, wenn Starts wurde. Wenn eine Anwendung eine andere Version von LOG4J verwenden möchte, kann sie nicht erreicht werden, wenn sie über Eltern verwendet wird, da die Klasse in LOG4J bereits im übergeordneten Loader geladen ist. Wenn jedoch übergeordnetes übergeordnetes Last verwendet wird, priorisiert der Klassenlader, der für das Laden der Anwendung verantwortlich ist, das Laden einer anderen Version von Log4J.
Sichtbarkeitsprinzip
Die Sichtbarkeit jeder Klasse zum Klassenlader ist unterschiedlich, wie in der folgenden Abbildung gezeigt.
Um das Wissen zu erweitern, nutzt OSGI diese Funktion. Jedes Bundle wird von einem separaten Klassenlader geladen, sodass jeder Klassenlader eine Version einer bestimmten Klasse laden kann, sodass das gesamte System mehrere Versionen einer Klasse verwenden kann.
Das Prinzip der Einzigartigkeit
Jede Klasse ist höchstens einmal in einem Lader geladen.
Erweitertes Wissen 1: Um genau zu sein, bezieht sich das Singleton -Muster auf ein Singleton -Objekt, das nur eine Kopie einer Klasse in einer Reihe von Klassenladern ist.
Erweitertes Wissen 2: Eine Klasse kann von mehreren Klassenladern geladen werden. Jedes Klassenobjekt befindet sich in seinem eigenen Namespace. Beim Vergleich des Klassenobjekts oder des Typs, das die Instanz konvertieren, wird der jeweilige Namespace gleichzeitig verglichen, z. B.:
Die KLASS -Klasse wird von ClassLadera geladen, vorausgesetzt, das Klassenobjekt ist Klassa; Es wird von ClassloaderB geladen, vorausgesetzt, das Klassenobjekt ist Klassb, dann ist Klassa nicht gleich KLASSB. Gleichzeitig wird eine ClassCastException -Ausnahme ausgelöst, wenn die Instanz von Classa an KLASSB abgestoßen wird.
Warum brauchen Sie einen personalisierten Klassenlader? Personalisierter Klassenlader verleiht der Java -Sprache viel Flexibilität. Die Hauptverwendungen sind:
1. Klassen können an mehreren Stellen geladen werden, z. B. im Netzwerk, in der Datenbank oder sogar sofort kompilierte Quelldateien.
2. Nach der Personalisierung kann der Klassenlader eine bestimmte Version der Klassendatei im Prinzip zur Laufzeit laden.
3. Nach der Personalisierung kann der Klassenlader einige Klassen dynamisch entladen.
V.
Implizite und explizite Belastung von Klassen
Implizite Belastung: Wenn auf eine Klasse verwiesen, vererbt oder instanziiert wird, wird sie implizit geladen. Wenn die Last ausfällt, wird NoclassDeffoundError geworfen.
Explizites Laden: Verwenden Sie die folgende Methode. Wenn die Last fehlschlägt, wird eine ClassNotFoundException geworfen.
cl.loadClass (), CL ist eine Instanz des Klassenladers;
Class.forname (), lädt den Klassenlader der aktuellen Klasse.
Wenn die Ausführung des statischen Blocks der Klasse die folgenden Klassen hat:
Paket cn.fengd; öffentliche Klasse Dummy {static {System.out.println ("hi"); }} Erstellen Sie eine andere Testklasse:
Paket cn.fengd; public class ClassloadTest {public static void main (String [] args) löst die InstantiationException aus, Ausnahme {try { / * * verschiedene Arten des Ladens. */ Class c = classLoloadtest.class.getClassloader (). LoadClass ("cn.fengd.dummy"); } catch (Ausnahme e) {// Todo automatisch generierter Block E. printstacktrace (); }}}Wie effektiv ist es nach dem Laufen?
Was ist, wenn es durch class.forname ersetzt wird ("cn.fengd.dummy"); oder New Dummy ()?
Hallo, wird ausgegeben.
Häufig gestellte Fragen:
1. Ist der angegebene Typ von verschiedenen Klassenladern immer noch den gleichen Typ geladen?
In Java verwendet eine Klasse ihren voll qualifizierten Klassennamen als Kennung. Der hier genannte genaue Übereinstimmungsname enthält den Paketnamen und den Klassennamen. In der JVM verwendet eine Klasse jedoch ihren vollständigen Namen und eine Instanz des Ladeklassenklassenloaders als eindeutige Bezeichner. Die von verschiedenen Klassenladern geladenen Klassen werden in verschiedenen Namespaces platziert. Wir können zwei benutzerdefinierte Klassenlader verwenden, um einen benutzerdefinierten Typ zu laden (beachten Sie, dass der Bytecode des benutzerdefinierten Typs nicht in den Systempfad oder den Erweiterungspfad eingerichtet wird. Andernfalls wird er zuerst vom Systemklassenlader oder dem Lader der Erweiterungsklasse geladen). Anschließend werden die beiden Klasseninstanzen verwendet, um Java.Lang.Object.equals (…) Beurteilungen zu machen. Dies kann durch das Schreiben von zwei benutzerdefinierten Klassenladern erfolgen, um den gleichen benutzerdefinierten Typ zu laden und dann ein Urteil zu fällen. Gleichzeitig können Sie das Laden von Java testen und dann die Testergebnisse vergleichen und testen.
2. Rufen Sie direkt die Methode der Klasse.forname (String -Name) im Code an. Welcher Klassenloader löst das Ladeverhalten der Klasse aus?
Class.ForName (String -Name) verwendet den Klassenlader, der die Klasse zum Laden der Klasse standardmäßig aufruft. Lassen Sie uns den entsprechenden JDK -Code direkt analysieren:
//java.lang.class.java publicstatic class <?> forname (String className) löst classNotFoundException {return forname0 (className, true, classloader.getCallerClassader ());} // java.lang.lassoader.java/// /////Lowers zurück, die der Invoker -Beleahung zurückgibt. getCallerClassLoader () {// den Typ der Anrufklasse (Anrufer) Klassen caller = reflection.getCallerClass (3) abrufen; // Dies kann null sein, wenn die VM es anfordert, wenn (caller == null) {returnNull; } // Rufen Sie die lokale Methode in java.lang.class an, um den Klassenloader zu erhalten, der die aufrufende Klasse (Anrufer) return Caller.getClassloader0 () ();} // java.lang.class.java//native Implementierung der virtuellen Maschine, um den Klassenlader des aktuellen Klassenladers GetClassader -GetClassader -GetClassader -GetClassader zu erhalten,); 3.. Beim Schreiben eines benutzerdefinierten Klassenladers, wenn der übergeordnete Loader nicht eingestellt ist, wie lautet dann der übergeordnete Loader?
Wenn der Lader der übergeordneten Klassen nicht angegeben ist, wird der Systemklassenlader standardmäßig verwendet. Einige Leute verstehen es vielleicht nicht. Schauen wir uns nun die entsprechende Code -Implementierung von JDK an. Wie wir alle wissen, schreiben wir einen benutzerdefinierten Klassenlader direkt oder indirekt von der Zusammenfassung der Java.lang.Classader. Der entsprechende Standardkonstruktor ohne Parameter wird wie folgt implementiert:
// Auszug aus java.lang.classloader.javaProtected classloader () {SecurityManager Security = System.getSecurityManager (); if (Security! = null) {Security.CheckCreateClassloader (); } this.parent = getSystemClassLoader (); initialisiert = true;}Schauen wir uns die entsprechende Implementierung der GetSystemClassLoader () -Methode an:
privatestaticsynchronizedVoid InitSystemClassloader () {// ... sun.misc.launcher l = sun.misc.launcher.getlauncher (); scl = l.getClassloader (); // ...}Wir können einfachen Testcode schreiben, um ihn zu testen:
System.out.println (sun.misc.launcher.getLauncher (). GetClassloader ());
Die entsprechende Ausgabe dieser Maschine ist wie folgt:
sun.misc.launcher$appclassloader@197d257
Wir können nun glauben, dass der Standardklassenlader der Systemklasse der Systemklassenlader ist, wenn der Lader der benutzerdefinierten Klassen nicht den Lader der übergeordneten Klassen angibt. Gleichzeitig können wir folgende Schlussfolgerungen ziehen:
Sofortiger benutzerdefinierter Klassenloader gibt den übergeordneten Klassenloader nicht an, dann können die Klassen an den folgenden drei Stellen ebenfalls geladen werden:
(1) Klassen unter <java_runtime_home>/lib
(2) Klassen an dem von der Systemvariablen java.ext.dir angegebenen Ort
(3) Klassen unter dem aktuellen Projektklassenpfad oder in dem von der Systemvariablen Java.Class.Path angegebenen Ort
4. Was wird beim Schreiben eines benutzerdefinierten Klassenladers passieren, wenn der Lader der Elternklasse gezwungen ist, Null zu sein? Wenn der benutzerdefinierte Klassenloader die angegebene Klasse nicht laden kann, fällt es definitiv fehl?
Die JVM-Spezifikation sieht vor, dass der Lader der Startklasse automatisch auf den übergeordneten Klassenlader des aktuellen, benutzerdefinierten Klassenladers eingestellt wird (dieses Problem wurde zuvor analysiert), wenn der benutzerdefinierte Klassenlader den übergeordneten Klassenlader auf den neuesten Stand der übergeordneten Klasse erzwingt. Gleichzeitig können wir folgende Schlussfolgerungen ziehen:
Wenn der sofortige benutzerdefinierte Klassenloader den übergeordneten Klassenloader nicht angibt, kann er die Klasse auch unter <Java_Runtime_Home>/lib laden, aber die Klasse unter <java_runtime_home>/lib/ext-Verzeichnis kann zu diesem Zeitpunkt nicht geladen werden.
Hinweis: Die abgeleiteten Schlussfolgerungen der Fragen 3 und 4 basieren auf dem benutzerdefinierten Klassenlader selbst, der die Standarddelegationslogik von Java.lang.Classloader.loadClass (…) fortsetzt. Wenn der Benutzer diese Standarddelegationslogik ändert, ist die obige Schlussfolgerung möglicherweise nicht gültig. Weitere Informationen finden Sie in Frage 5.
5. Was sind die allgemeinen Aufmerksamkeitspunkte beim Schreiben benutzerdefinierter Klassenlader?
(1) Versuchen Sie im Allgemeinen, die Delegationslogik in der vorhandenen Lastklasse (…) -Methode nicht zu überschreiben. Im Allgemeinen erfolgt dies in Versionen vor JDK 1.2, und es stellt sich heraus, dass der Standardklassenlader des Systems sehr wahrscheinlich nicht ordnungsgemäß funktioniert. In der JVM -Spezifikation und der JDK -Dokumentation (1.2 oder spätere Versionen) wird nicht empfohlen, dass Benutzer die Lastklasse -Methode (…) überschreiben. Im Gegensatz dazu werden Entwickler deutlich daran erinnert, die FindClass (…) -Logik bei der Entwicklung eines benutzerdefinierten Klassenladers zu überschreiben. Nehmen Sie ein Beispiel, um das Problem zu überprüfen:
// Benutzer benutzerdefinierte Klasse lader laderClassloader.java (Overwrite LoadClass Logic) publicClassWrongClasLoadeXTends Classloader {public class <?> LoadClass (String -Name) löst ClassNotFoundException {returnthis.findClass (Name) aus; } Protected Class <?> FindClass (String -Name) löst ClassNotFoundException aus {// Angenommen, hier ist nur in ein bestimmtes Verzeichnis als das Projekt d: /Bibliothek, um den klassenspezifischen Implementierungscode weggelassen}} zu laden}}Durch die vorherige Analyse wissen wir bereits, dass die Standardeinstellung des benutzerdefinierten Klassenladers (WrongClassloader)
Der anerkannte Klassenlader ist ein Systemklassenlader, aber die Schlussfolgerungen der vier Arten von Problemen sind jetzt nicht gültig. Jeder kann
Testen Sie es einfach aus, jetzt <java_runtime_home>/lib, <java_runtime_home>/lib/ext und
Die Klassen auf dem Programmklassenpfad können nicht geladen werden.
Frage 5 Testcode 1
publicCass -WrongClassloadtest {publicStaticVoid main (String [] args) {try {WrongClassLoader loader = new WrongClassLoader (); Klasse klassifiziert = lader.loadClass ("beans.account"); System.out.println (classLode.getName ()); System.out.println (classLode.getClassloader ()); } catch (Ausnahme e) {e.printstacktrace (); }}}(Anmerkung: D: "Klassen" Beans "Account.Class physisch existiert)
Ausgangsergebnis:
java.io.filenotfoundException: d: "classes" java "lang" Objekt.Class (das System kann den angegebenen Pfad nicht finden) unter java.io.fileinputStream.open (native Methode) unter Java.io.fileInputStream. WrongClassloader.FindClass (WrongClassloader.java:40) bei WrongClassloader.loadClass (LaDaClassloader.java:29) bei Java.lang.Classloader.loadClassinternal (Classloader.java:319) at Java.lang.Classinternal.Defineclass java.lang.ClassLoader.defineClass(ClassLoader.java:620) at java.lang.ClassLoader.defineClass(ClassLoader.java:400) at WrongClassLoader.findClass(WrongClassLoader.java:43) at WrongClassLoader.loadClass(WrongClassLoader.java:29) at WrongClassloadtest.main (WrongClassloadtest.java:27) Ausnahme In Thread "Haupt" java.lang.noclassDeffoundError: Java/Lang/Objekt bei Java.lang.Classloader.defineclass1 (native Methode) bei Java.lang.Classeloader.definecLASS (Classloader.Java: 620) in der. java.lang.classloader.defineclass (classloader.java:400) unter WrongClassloader.findClass (WrongClassloader.java:43) bei WrongClassloader.loadClass (WrongClassloader.java:29) bei falschenClassloader.Main (FalschClassLass.java:27).
Dies bedeutet, dass selbst der Supertyp -Java.lang.Object des zu geladenen Typs nicht geladen werden kann. Die hier verursachten logischen Fehler, die durch die Überschreibung der Lastklasse (…) verursacht wurden, sind offensichtlich relativ einfach, und die logischen Fehler können tatsächlich viel komplizierter sein.
Frage 5 Test 2
// Benutzer benutzerdefinierte Klasse Loader Ladera LaceClassLoader.java (Überschreiben Sie keine LoadClass -Logik über.
Führen Sie den Testcode wie folgt aus, nachdem Sie den benutzerdefinierten Klassenloader -Code fehlerklasseloader.java geändert haben.
Beans.accountWongClassloader@1c78e57
Dies zeigt, dass Beans.account erfolgreich geladen und vom benutzerdefinierten Klassenlader -Laderader geladen wird.
Ich glaube nicht, dass es die Gründe dafür erklären muss, und Sie sollten in der Lage sein, sie zu analysieren.
(2) Richten Sie den übergeordneten Klassenlader durch die Analyse von Frage 4 und Frage 5 korrekt ein. Ich persönlich denke, dies ist der wichtigste Punkt bei der Anpassung von Benutzerklassenladern, aber es wird oft ignoriert oder leicht zugeführt. Mit der Analyse des vorherigen JDK -Code als Grundlage kann jeder jetzt alle Beispiele geben.
(3) Stellen Sie die logische Korrektheit der FindClass (String) -Methode sicher, versuchen Sie, die zu definierten Klassenlader genau zu verstehen, und stellen Sie sicher, dass der entsprechende Bytecode -Inhalt in größtem Umfang erhalten werden kann.
6. Wie können Sie bestimmen, welche Pfade der Lader der Systemklasse laden kann?
Zunächst können Sie den Classloader.GetSystemClassloader () oder andere Methoden direkt aufrufen, um den Lader der Systemklasse (der Lader der Systemklasse und der Erweiterungsklassen selbst aus der URLClassloader) abzuleiten, und Sie können sie erhalten, indem Sie die Geturls () -Methode in URLClassloader aufrufen.
Zweitens können Sie die Eintragsinformationen auf dem aktuellen Klassenpfad direkt anzeigen, indem Sie das Systemattribut Java.Class.Path erhalten. System.getProperty ("java.class.path")
7. Wie kann ich bestimmen, welche Pfade der Lader der Standardverlängerungsklasse laden kann?
Methode eins:
try {url [] exturls = ((urlClassloader) classloader.getSystemClassLoader (). getParent ()). getUrls (); für (int i = 0; i <exturls.length; i ++) {System.out.println (exturls [i]); }} catch (Ausnahme e) {//…}Die entsprechende Ausgabe dieser Maschine ist wie folgt:
Datei:/d: /demo/jdk1.5.0_09/jre/lib/ext/dnsns.jarfile:/d: /demo/jdk1.5.0_09/jre/lib/ext/localedata.jarfile :/D: /demo/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jarfile:/d:/demo/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar