Heim  >  Artikel  >  Java  >  Eine eingehende Analyse des Klassenladers Class Loader in Java

Eine eingehende Analyse des Klassenladers Class Loader in Java

高洛峰
高洛峰Original
2017-01-13 09:33:351597Durchsuche

Der Prozess des Klassenladens
Die Hauptaufgabe des Klassenladers besteht darin, Klassendateien in die JVM zu laden. Wie in der Abbildung unten gezeigt, ist der Vorgang in drei Schritte unterteilt:

1. Laden: Suchen Sie die zu ladende Klassendatei und laden Sie ihren Byte-Stream in die JVM Die zu ladende Klassendatei. Eine Klasse weist die grundlegendste Speicherstruktur zu, um ihre Informationen wie Eigenschaften, Methoden und referenzierte Klassen zu speichern. Zu diesem Zeitpunkt ist die Klasse noch nicht verfügbar.
(1) Überprüfung: Überprüfen Sie den geladenen Byte-Stream, z. B. Format und Sicherheit. (2) Speicherzuweisung: Bereiten Sie Speicherplatz für diese Klasse vor, um ihre Attribute darzustellen. Methoden und referenzierte Klassen;
(3) Analyse: Laden Sie andere Klassen, auf die diese Klasse verweist, z. B. übergeordnete Klassen, implementierte Schnittstellen usw.
3. Initialisierung: Weisen Sie Klassenvariablen Werte zu.

深入解析Java中的Class Loader类加载器Stufen von Klassenladern

Über der gepunkteten Linie in der Abbildung unten sind mehrere wichtige Klassenlader aufgeführt, die vom JDK bereitgestellt werden. Die detaillierte Beschreibung lautet wie folgt:


(1) Bootstrap-Klassenlader: Wenn Sie eine Klasse starten, die die Hauptfunktion enthält, laden Sie das JAR-Paket in das Verzeichnis JAVA_HOME/lib oder das durch -Xbootclasspath angegebene Verzeichnis.

(2) Erweiterungsklassenlader: Laden Sie JAVA_HOME /lib/ext-Verzeichnis oder -Djava.ext.dirs-JAR-Paket im angegebenen Verzeichnis.

(3) Systemklassenlader: Laden Sie Klassen oder JAR-Pakete in das durch classpath oder -Djava.class.path angegebene Verzeichnis.

深入解析Java中的Class Loader类加载器Was Sie wissen müssen:

1. Außer Bootstrap Class Loader sind andere Klassenlader java.lang.ClassLoader Unterklasse

2.Der Bootstrap-Klassenlader ist in Java nicht implementiert. Wenn Sie keinen personalisierten Klassenlader verwenden, ist java.lang.String.class.getClassLoader() null und das übergeordnete Element des Erweiterungsklassenladers wird geladen ist ebenfalls null;

3. Mehrere Möglichkeiten, den Klassenlader zu erhalten:
(1) Den Bootstrap-Klassenlader abrufen: Beim Versuch, den Bootstrap-Klassenlader abzurufen, muss das Ergebnis null sein. Sie können dies auf folgende Weise überprüfen: Verwenden Sie die getClassLoader-Methode des Klassenobjekts im rt.jar-Paket, z. B. java.lang.String.class.getClassLoader(), um den Extention Class Loader abzurufen oder abzurufen, und rufen Sie dann auf getParent-Methode, um es zu erhalten;
( 2) Erhalten Sie den Extention Class Loader: Verwenden Sie die getClassLoader-Methode des Klassenobjekts im JAR-Paket im Verzeichnis JAVA_HOME/lib/ext oder rufen Sie zuerst den System Class Loader ab und rufen Sie ihn dann ab über seine getParent-Methode;
(3) Rufen Sie den Systemklassenlader ab: Rufen Sie die getClassLoader-Methode des Klassenobjekts auf, das die Hauptfunktion enthält, oder rufen Sie Thread.currentThread().getContextClassLoader() auf oder rufen Sie ClassLoader.getSystemClassLoader() innerhalb der Hauptfunktion auf Funktion;
(4) Benutzerdefinierten Klassenlader abrufen: Rufen Sie die getClassLoader-Methode des Klassenobjekts auf oder rufen Sie Thread.currentThread().getContextClassLoader() auf >


Funktionsprinzip des Klassenladers

1. Proxy-Prinzip

2. Sichtbarkeitsprinzip
3. Eindeutigkeitsprinzip

4. Proxy-Prinzip

Das Proxy-Prinzip bedeutet dass ein Klassenlader seinen übergeordneten Klassenlader auffordert, eine Klasse zu laden, wie in der Abbildung unten gezeigt.



Warum den Proxy-Modus verwenden? Erstens kann dadurch das wiederholte Laden einer Klasse reduziert werden. (Gibt es noch andere Gründe?)


Leicht missverstanden: 深入解析Java中的Class Loader类加载器

Es wird allgemein angenommen, dass die Proxy-Reihenfolge des Klassenladers „Parent First“ ist, das heißt:

1. Beim Laden einer Klasse prüft der Klassenlader zunächst, ob er geladen wurde. Andernfalls wird der übergeordnete Lader aufgefordert, den Vorgang durchzuführen Der Vorgang von 1. Gehe zum Bootstrap-Klassenlader.
3. Wenn der Bootstrap-Klassenlader die Klasse nicht lädt, wird er zurückkehren, wenn der Ladevorgang fehlschlägt , und der untergeordnete Lader wird es laden;
Dieses Verständnis gilt für Lader wie Bootstrap Class Loader, Extension Class Loader und System Class Loader, einige personalisierte Lader sind jedoch nicht korrekt. Beispielsweise sind einige von IBM Web Sphere Portal Server implementierte Klassenlader Parent Last, das Kind Wenn der Ladevorgang fehlschlägt, wird der übergeordnete Loader aufgerufen: Wenn Sie erwarten, dass eine bestimmte Version von log4j von allen Anwendungen verwendet wird, legen Sie sie in die WAS_HOME-Bibliothek beim Start. Wenn eine Anwendung eine andere Version von log4j verwenden möchte, kann dies bei Verwendung von Parent First nicht erreicht werden, da die Klassen in log4j bereits im übergeordneten Loader geladen wurden. Wenn Sie jedoch Parent Last verwenden, lädt der für das Laden der Anwendung verantwortliche Klassenlader zuerst eine andere Version von log4j.


Sichtbarkeitsprinzip

Die Sichtbarkeit jeder Klasse für den Klassenlader ist unterschiedlich, wie in der folgenden Abbildung dargestellt.


OSGi erweitert das Wissen und nutzt 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 Klasse.

深入解析Java中的Class Loader类加载器

Einzigartigkeitsprinzip
<br/>
Jede Klasse kann höchstens einmal in einem Loader geladen werden.


Erweitertes Wissen 1: Um genau zu sein, bezieht sich der Singleton, auf den sich das Singleton-Muster bezieht, nur auf eine Kopie eines Objekts einer bestimmten Klasse in einer Gruppe von Klassenladern.

Erweitertes Wissen 2: Eine Klasse kann von mehreren Klassenladern geladen werden. Jedes Klassenobjekt befindet sich in einem eigenen Namensraum. Beim Vergleich von Klassenobjekten oder bei der Typkonvertierung für Instanzen werden gleichzeitig deren jeweilige Namen verglichen . Leerzeichen, zum Beispiel:

Die Klass-Klasse wird von ClassLoaderA geladen, vorausgesetzt, das Klassenobjekt ist KlassA; sie wird auch von ClassLoaderB geladen, vorausgesetzt, das Klassenobjekt ist KlassB, dann ist KlassA nicht gleich KlassB. Gleichzeitig wird ClassCastException ausgelöst, wenn eine Instanz von ClassA in KlassB umgewandelt wird.

Warum personalisierter Klassenlader

Der personalisierte Klassenlader verleiht der Java-Sprache viel Flexibilität. Seine Hauptanwendungen sind:

1 Klassen können von mehreren Orten geladen werden, beispielsweise vom Netzwerk Datenbank oder kompilieren Sie die Quelldatei sofort, um die Klassendatei zu erhalten.

2. Der personalisierte Klassenlader kann dynamisch eine bestimmte Version der Klassendatei laden Entladen Sie einige Klassen.

4. Nach der Personalisierung kann der Klassenlader die Klasse entschlüsseln und dekomprimieren.


Implizites und explizites Laden von Klassen

Implizites Laden: Wenn eine Klasse referenziert, geerbt oder instanziiert wird, wird sie implizit geladen. Wenn das Laden fehlschlägt, wird ein NoClassDefFoundError ausgegeben.


Explizites Laden: Verwenden Sie die folgende Methode. Wenn das Laden fehlschlägt, wird eine ClassNotFoundException ausgelöst.

cl.loadClass(), cl ist eine Instanz des Klassenladers;

Class.forName(), verwendet zum Laden den Klassenlader der aktuellen Klasse.

Ausführung des statischen Klassenblocks

Angenommen, es gibt die folgende Klasse:


Erstellen Sie eine weitere Testklasse:

package cn.fengd; 
  
public class Dummy { 
 static { 
 System.out.println("Hi"); 
 } 
}

Was ist der Effekt nach der Ausführung?

package cn.fengd; 
  
public class ClassLoaderTest { 
  
 public static void main(String[] args) throws InstantiationException, Exception { 
  
 try { 
  /* 
  * Different ways of loading. 
  */
  Class c = ClassLoaderTest.class.getClassLoader().loadClass("cn.fengd.Dummy"); 
 } catch (Exception e) { 
  // TODO Auto-generated catch block 
  e.printStackTrace(); 
 } 
 } 
  
}
Hallo wird nicht ausgegeben. Es ist ersichtlich, dass das Klassenobjekt nach der Verwendung von „loadClass“ nicht initialisiert wird Klasse wird instanziiert.

Wenn Sie die Ladeanweisung ändern, wird Class c = Class.forName("cn.fengd.Dummy", false, ClassLoader.getSystemClassLoader());

Hi nicht ausgegeben. Denn der Parameter false bedeutet, dass das Objekt dieser Klasse nicht initialisiert werden muss.

Wenn c.newInstance() nach der Load-Anweisung hinzugefügt wird, erfolgt eine Hi-Ausgabe und das Klassenobjekt wird initialisiert nur, wenn die Klasse instanziiert wird.

Was ist, wenn Sie es in Class.forName("cn.fengd.Dummy"); oder new Dummy() ändern?

gibt alle „Hallo“ aus.

Analyse häufig gestellter Fragen:

1.由不同的类加载器加载的指定类型还是相同的类型吗?
在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.我们可以用两个自定义类加载器去加载某自定义类型(注意,不要将自定义类型的字节码放置到系统路径或者扩展路径中,否则会被系统类加载器或扩展类加载器抢先加载),然后用获取到的两个Class实例进行java.lang.Object.equals(…)判断,将会得到不相等的结果。这个大家可以写两个自定义的类加载器去加载相同的自定义类型,然后做个判断;同时,可以测试加载java.*类型,然后再对比测试一下测试结果。

2.在代码中直接调用Class.forName(String name)方法,到底会触发那个类加载器进行类加载行为?
Class.forName(String name)默认会使用调用类的类加载器来进行类加载。我们直接来分析一下对应的jdk的代码:

//java.lang.Class.java
 
 publicstatic Class<?> forName(String className)throws ClassNotFoundException {
 
return forName0(className, true, ClassLoader.getCallerClassLoader());
 
}
 
//java.lang.ClassLoader.java
 
// Returns the invoker&#39;s class loader, or null if none.
 
static ClassLoader getCallerClassLoader() {
 
  // 获取调用类(caller)的类型
 
 Class caller = Reflection.getCallerClass(3);
 
  // This can be null if the VM is requesting it
 
 if (caller == null) {
 
  returnnull;
 
 }
 
 // 调用java.lang.Class中本地方法获取加载该调用类(caller)的ClassLoader
 
 return caller.getClassLoader0();
 
}
 
//java.lang.Class.java
 
//虚拟机本地实现,获取当前类的类加载器
native ClassLoader getClassLoader0();

3.在编写自定义类加载器时,如果没有设定父加载器,那么父加载器是?
在不指定父类加载器的情况下,默认采用系统类加载器。可能有人觉得不明白,现在我们来看一下JDK对应的代码实现。众所周知,我们编写自定义的类加载器直接或者间接继承自java.lang.ClassLoader抽象类,对应的无参默认构造函数实现如下:

//摘自java.lang.ClassLoader.java
protected ClassLoader() {
 
  SecurityManager security = System.getSecurityManager();
 
  if (security != null) {
 
  security.checkCreateClassLoader();
 
  }
 
  this.parent = getSystemClassLoader();
 
  initialized = true;
 
}

我们再来看一下对应的getSystemClassLoader()方法的实现:

privatestaticsynchronizedvoid initSystemClassLoader() {
 
  //...
 
  sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
 
  scl = l.getClassLoader();
 
  //...
 
}

我们可以写简单的测试代码来测试一下:

System.out.println(sun.misc.Launcher.getLauncher().getClassLoader());

本机对应输出如下:

sun.misc.Launcher$AppClassLoader@197d257

所以,我们现在可以相信当自定义类加载器没有指定父类加载器的情况下,默认的父类加载器即为系统类加载器。同时,我们可以得出如下结论:

即时用户自定义类加载器不指定父类加载器,那么,同样可以加载如下三个地方的类:

(1)6e5475ece90bdb5cedfc1a16b6632d24/lib下的类

(2)9da487fed862e2e75b98522999bd48b8/lib/ext下或者由系统变量java.ext.dir指定位置中的类

(3)当前工程类路径下或者由系统变量java.class.path指定位置中的类

4.在编写自定义类加载器时,如果将父类加载器强制设置为null,那么会有什么影响?如果自定义的类加载器不能加载指定类,就肯定会加载失败吗?
JVM规范中规定如果用户自定义的类加载器将父类加载器强制设置为null,那么会自动将启动类加载器设置为当前用户自定义类加载器的父类加载器(这个问题前面已经分析过了)。同时,我们可以得出如下结论:
即时用户自定义类加载器不指定父类加载器,那么,同样可以加载到6e5475ece90bdb5cedfc1a16b6632d24/lib下的类,但此时就不能够加载6e5475ece90bdb5cedfc1a16b6632d24/lib/ext目录下的类了。
    说明:问题3和问题4的推断结论是基于用户自定义的类加载器本身延续了java.lang.ClassLoader.loadClass(…)默认委派逻辑,如果用户对这一默认委派逻辑进行了改变,以上推断结论就不一定成立了,详见问题5。

5.编写自定义类加载器时,一般有哪些注意点?
(1)一般尽量不要覆写已有的loadClass(…)方法中的委派逻辑
一般在JDK 1.2之前的版本才这样做,而且事实证明,这样做极有可能引起系统默认的类加载器不能正常工作。在JVM规范和JDK文档中(1.2或者以后版本中),都没有建议用户覆写loadClass(…)方法,相比而言,明确提示开发者在开发自定义的类加载器时覆写findClass(…)逻辑。举一个例子来验证该问题:

//用户自定义类加载器WrongClassLoader.Java(覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {
 
 public Class<?> loadClass(String name) throws ClassNotFoundException {
 
  returnthis.findClass(name);
 
 }
 
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 
  //假设此处只是到工程以外的特定目录D:/library下去加载类
 
  具体实现代码省略
 
 }
 
}

    通过前面的分析我们已经知道,用户自定义类加载器(WrongClassLoader)的默

       认的类加载器是系统类加载器,但是现在问题4种的结论就不成立了。大家可以简

       单测试一下,现在6e5475ece90bdb5cedfc1a16b6632d24/lib、9da487fed862e2e75b98522999bd48b8/lib/ext和工

       程类路径上的类都加载不上了。

问题5测试代码一

publicclass WrongClassLoaderTest {
 
 publicstaticvoid main(String[] args) {
 
  try {
 
  WrongClassLoader loader = new WrongClassLoader();
 
  Class classLoaded = loader.loadClass("beans.Account");
 
  System.out.println(classLoaded.getName());
 
  System.out.println(classLoaded.getClassLoader());
 
  } catch (Exception e) {
 
  e.printStackTrace();
 
  }
 
 }
 
}

(说明:D:"classes"beans"Account.class物理存在的)

输出结果:

java.io.FileNotFoundException: D:"classes"java"lang"Object.class (系统找不到指定的路径。)
 
 at java.io.FileInputStream.open(Native Method)
 
 at java.io.FileInputStream.<init>(FileInputStream.java:106)
 
 at WrongClassLoader.findClass(WrongClassLoader.java:40)
 
 at WrongClassLoader.loadClass(WrongClassLoader.java:29)
 
 at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:319)
 
 at java.lang.ClassLoader.defineClass1(Native Method)
 
 at 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 WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)
 
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
 
 at java.lang.ClassLoader.defineClass1(Native Method)
 
 at 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 WrongClassLoaderTest.main(WrongClassLoaderTest.java:27)

   

这说明,连要加载的类型的超类型java.lang.Object都加载不到了。这里列举的由于覆写loadClass(…)引起的逻辑错误明显是比较简单的,实际引起的逻辑错误可能复杂的多。
问题5测试二

//用户自定义类加载器WrongClassLoader.Java(不覆写loadClass逻辑)
publicclassWrongClassLoaderextends ClassLoader {
 
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 
  //假设此处只是到工程以外的特定目录D:/library下去加载类
 
  具体实现代码省略
 
 }
 
}

将自定义类加载器代码WrongClassLoader.Java做以上修改后,再运行测试代码,输出结果如下:

beans.Account
 
WrongClassLoader@1c78e57

这说明,beans.Account加载成功,且是由自定义类加载器WrongClassLoader加载。

这其中的原因分析,我想这里就不必解释了,大家应该可以分析的出来了。

(2)正确设置父类加载器
通过上面问题4和问题5的分析我们应该已经理解,个人觉得这是自定义用户类加载器时最重要的一点,但常常被忽略或者轻易带过。有了前面JDK代码的分析作为基础,我想现在大家都可以随便举出例子了。
(3)保证findClass(String )方法的逻辑正确性
事先尽量准确理解待定义的类加载器要完成的加载任务,确保最大程度上能够获取到对应的字节码内容。

6.如何在运行时判断系统类加载器能加载哪些路径下的类?
一是可以直接调用ClassLoader.getSystemClassLoader()或者其他方式获取到系统类加载器(系统类加载器和扩展类加载器本身都派生自URLClassLoader),调用URLClassLoader中的getURLs()方法可以获取到;

二是可以直接通过获取系统属性java.class.path 来查看当前类路径上的条目信息 , System.getProperty("java.class.path")

7.如何在运行时判断标准扩展类加载器能加载哪些路径下的类?
方法之一:

try {
  URL[] extURLs = ((URLClassLoader)ClassLoader.getSystemClassLoader().getParent()).getURLs();
 
  for (int i = 0; i < extURLs.length; i++) {
 
   System.out.println(extURLs[i]);
 
  }
 
 } catch (Exception e) {//…}

       本机对应输出如下:

file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/dnsns.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/localedata.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunjce_provider.jar
 
file:/D:/DEMO/jdk1.5.0_09/jre/lib/ext/sunpkcs11.jar

更多深入解析Java中的Class Loader类加载器相关文章请关注PHP中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn