Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung des Klassenladecodes in Java

Detaillierte Erläuterung des Klassenladecodes in Java

黄舟
黄舟Original
2017-06-18 09:44:271525Durchsuche

In diesem Artikel werden hauptsächlich die relevanten Informationen zum Klassenlader in Java ausführlich vorgestellt, die einen gewissen Referenzwert haben:

Von der dynamischen Natur von Java bis zum Klassenlademechanismus

Java ist eine dynamische Sprache. Wie ist diese „Dynamik“ zu verstehen? Mit anderen Worten: Welche Eigenschaften muss eine Sprache haben, um als dynamische Sprache bezeichnet zu werden? Für Java verstehe ich es so.

JVM (Java Virtual Machine) führt keine lokalen Maschinencodeanweisungen aus, sondern führt eine Art von Anweisung namens Bytecode aus (in Klassendateien vorhanden). Dies erfordert, dass die virtuelle Maschine die relevanten Klassendateien in den Speicher lädt, bevor sie den Bytecode tatsächlich ausführt. Die virtuelle Maschine lädt nicht alle erforderlichen Klassendateien auf einmal, da sie bei ihrer Ausführung nicht weiß, welche Klassendateien in Zukunft verwendet werden. Bei jeder Verwendung einer Klasse wird die zu dieser Klasse gehörende Klassendatei zur Laufzeit „dynamisch“ geladen. Dies ist der Hauptgrund, warum Java als dynamische Sprache bezeichnet wird. Zusätzlich zum dynamischen Laden von Klassen werden Klassen auch dynamisch initialisiert und dynamisch verknüpft. Dynamische Initialisierung und dynamische Verknüpfung werden in anderen Artikeln vorgestellt. In diesem Artikel geht es nur um das Laden von Klassen.

Der in diesem Artikel vorgestellte Klassenlader (ClassLoader) ist für das Laden von Klassen in der JVM verantwortlich. Daher ist der Klassenlader eine unverzichtbare und wichtige Komponente der JVM.

Klassenlader in Java und das Funktionsprinzip von Klassenladern

Es gibt drei Klassenlader in Java (bezogen auf Javase). Jeder Klassenlader hat bei seiner Erstellung sein entsprechendes Verzeichnis angegeben, was bedeutet, dass bestimmt wird, wo jeder Klassenlader Klassen lädt. Ich denke, dass es in der ClassLoader-Klasse Methoden wie getTargetPath() geben sollte in der JDK-Dokumentation und stellte fest, dass sie nicht verfügbar waren. Im Folgenden sind diese drei Klassenlader und ihre entsprechenden Pfade aufgeführt:

* AppClassLoader – Laden Sie die Klassen in den durch den Klassenpfad angegebenen Pfad
* ExtClassLoader – Laden Sie das Verzeichnis jre/lib/ext oder java.ext Klassen im durch die .dirs-Systemeigenschaft definierten Verzeichnis
* BootStrap – Laden von Klassen in JRE/lib/rt.jar

Wie funktioniert also der Klassenlader? Sie können auf den Quellcode der ClassLoader-Klasse in JDK verweisen. Die Implementierung dieser Klasse verwendet das Vorlagenmethodenmuster. Die Methode „loadClass“ ruft die Methode „findClass“ auf und gibt sie zurück um die Methode defineClass aufzurufen, werden die zurückgegebenen Daten in Typinformationen verarbeitet, die erkannt werden können, wenn die virtuelle Maschine ausgeführt wird. Wenn wir also unseren eigenen Klassenlader entwickeln, müssen wir nur die ClassLoader-Klasse im JDK erben und die findClass-Methode überschreiben. Den Rest erledigt die übergeordnete Klasse. Einige andere Java-Plattformen haben ihre eigenen spezifischen Klassenlader entsprechend ihren eigenen Anforderungen implementiert, z. B. der Tomcat-Server auf der Javaee-Plattform, und die virtuelle Dalvik-Maschine auf der Android-Plattform definiert ebenfalls einen eigenen Klassenlader.

Es gibt zwei Möglichkeiten für eine virtuelle Maschine, Klassen zu laden, und die andere ist die Verwendung der Reflection-API, der Class.forName()-Methode Die Methode Class.forName( ) wird auch im ClassLoader verwendet. Die Implementierung der forName-Methode in der Class-Klasse ist wie folgt:


public static Class<?> forName(String name, boolean initialize,
  ClassLoader loader)
  throws ClassNotFoundException
 {
 if (loader == null) {
  SecurityManager sm = System.getSecurityManager();
  if (sm != null) {
 ClassLoader ccl = ClassLoader.getCallerClassLoader();
 if (ccl != null) {
  sm.checkPermission(
 SecurityConstants.GET_CLASSLOADER_PERMISSION);
 }
  }
 }
 return forName0(name, initialize, loader);
 }

 /** Called after security checks have been made. */
 private static native Class forName0(String name, boolean initialize,
   ClassLoader loader)
 throws ClassNotFoundException;

Drei Funktionen des Klassenladers

Der Klassenlader weist drei Merkmale auf, nämlich Delegation, Sichtbarkeit und Singleness. Die Einführung dieser drei Merkmale in anderen Artikeln lautet wie folgt:

* Der Delegationsmechanismus bezieht sich auf die Übergabe der Anforderung So laden Sie eine Klasse in den übergeordneten Klassenlader. Wenn der übergeordnete Klassenlader die Klasse nicht finden oder laden kann, laden Sie sie erneut.
* Das Prinzip der Sichtbarkeit besteht darin, dass der untergeordnete Klassenlader alle vom übergeordneten Klassenlader geladenen Klassen sehen kann, der übergeordnete Klassenlader jedoch nicht die vom untergeordneten Klassenlader geladenen Klassen.
* Das Einheitsprinzip bedeutet, dass eine Klasse nur einmal geladen wird. Dies liegt daran, dass der Delegierungsmechanismus sicherstellt, dass der untergeordnete Klassenlader die vom übergeordneten Klassenlader geladene Klasse nicht erneut lädt.

Unter diesen ist der Delegationsmechanismus die Grundlage. Dieser Mechanismus wird in anderen Materialien auch als übergeordnetes Delegationsmodell des Klassenladers bezeichnet, was eigentlich dasselbe bedeutet. Additivität und Einheit hängen vom Delegationsmechanismus ab.

Der folgende Code testet den Delegationsmechanismus des Klassenladers:


 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 
 ClassLoader extClassLoader = appClassLoader.getParent();
 System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
 //AppClassLoader的父加载器是ExtClassLoader
 
 System.out.println(extClassLoader.getParent()); //null
 //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的

Wie aus den Druckergebnissen ersichtlich ist, erfolgt das Laden Der Loader der Klasse, den wir selbst geschrieben haben, ist AppClassLoader, und der übergeordnete Loader von ExtClassLoader gibt Null zurück, was bedeutet, dass dieser Loader eng mit der virtuellen Maschine verbunden ist Maschine Beim Start werden die Klassen im JDK geladen. Es ist in C implementiert und verfügt über kein entsprechendes Java-Objekt, daher wird null zurückgegeben. Aber logischerweise ist BootStrap immer noch der übergeordnete Loader von ExtClassLoader. Das heißt, wann immer ExtClassLoader eine Klasse lädt, wird diese immer zum Laden an BootStrap delegiert.

系统类加载器和线程上下文类加载器 

在java中,还存在两个概念,分别是系统类加载器和线程上下文类加载器。 

其实系统类加载器就是AppClassLoader应用程序类加载器,它两个值得是同一个加载器,以下代码可以验证: 


 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
 System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的

 这两个类加载器对应的输出,不仅类名相同,连对象的哈希值都是一样的,这充分说明系统类加载器和应用程序类加载器不仅是同一个类,更是同一个类的同一个对象。

每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程的上下文类加载器, 也就是AppClassLoader。 


 new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
 }
 }).start();

这个子线程在执行时打印的信息为sun.misc.Launcher$AppClassLoader@19821f,可以看到和主线程中的AppClassLoader是同一个对象(哈希值相同)。 

也可以为线程设置特定的类加载器,这样的话,线程在执行时就会使用这个特定的类加载器来加载使用到的类。如下代码: 


 Thread th = new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
 }
 });
 
 th.setContextClassLoader(new ClassLoader() {});
 
 th.start();

在线程运行之前,为它设置了一个匿名内部类的类加载器对象,线程运行时,输出的信息为:jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74,也就是我们设置的那个类加载器对象。 

类加载器的可见性 

下面验证类加载器的可见性,也就是 子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。 

以下代码使用父加载器ExtClassLoader加载子加载器AppClassLoader路径下的类,由输出可知,是不可能实现的。 


 try {
 Class.forName("jg.zhang.java.testConcurrent.Person", true, 
  ClassLoaderTest.class.getClassLoader().getParent());
 System.out.println("1 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("1 -- 未找到类");
 }

输出为 :1 -- 未找到类 。说明抛出了ClassNotFoundException异常。原因是让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下,所以抛出ClassNotFoundException。所以父加载器不能加载应该被子加载器加载的类。也就是说这个类在父加载器中不可见。这种机制依赖于委派机制。 

下面代码使用子加载器AppClassLoader 加载父加载器BootStrap中的类,这是可以实现的。


 try {
 Class.forName("java.lang.String", true, 
  ClassLoaderTest.class.getClassLoader());
 System.out.println("2 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("2 -- 未找到类");
 }

 输出为:2 -- 类被加载。说明成功加载了String类。是因为在指定由AppClassLoader加载String类时,由AppClassLoader一直委派到BootStrap加载。虽然是由子加载器的父加载器加载的,但是也可以说,父加载器加载的类对于子加载器来说是可见的。这同样依赖于委派机制。其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了,这时再次加载,虚拟机发现已经加载,不会再重复加载。这同时也证明了类加载器的单一性。 

测试代码 

到此为止,类加载器的知识就全部讲完了。以下是整个测试代码: 


package jg.zhang.java.testclassloader;


/**
 * 参考ImportNew上的一篇文章<<类加载器的工作原理>>,
 * 文章地址:http://www.importnew.com/6581.html
 * 
 * Java类加载器基于三个机制:委托、可见性和单一性。
 * 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
 * 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
 * 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
 * 
 * 三种类加载器: 每个类加载器在创建的时候已经指定他们对应的目录, 也就是说每个类加载器去哪里加载类是确定的
 * 我认为在ClassLoader类中应该会有getTargetPath()之类的方法, 得到他们对应的路径,找了找jdk的文档,发现是没有的.
 * AppClassLoader -- 加载classpath指定的路径中的类
 * ExtClassLoader -- 加载jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下的类
 * BootStrap  -- 加载JRE/lib/rt.jar中的类
 * 
 * 
 * 
 * @author zhangjg
 *
 */
public class ClassLoaderTest {

 
 public static void main(String[] args) {
 test1();
 test2();
 test3();
 }

 /**
 * 验证线程上下文类加载器
 */
 private static void test3() {
 /**
 * 1 每个线程都会有一个上下文类加载器,由于在线程执行时加载用到的类,默认情况下是父线程
 * 的上下文类加载器, 也就是AppClassLoader
 */
 new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //sun.misc.Launcher$AppClassLoader@19821f
 }
 }).start();
 
 /**
 * 2 也可以给创建的线程设定特定的上下文类加载器
 */
 Thread th = new Thread(new Runnable() {
 
 @Override
 public void run() {
 ClassLoader threadcontextClassLosder = Thread.currentThread().getContextClassLoader();
 System.out.println(threadcontextClassLosder); //jg.zhang.java.testclassloader.ClassLoaderTest$3@1b67f74
 }
 });
 
 th.setContextClassLoader(new ClassLoader() {});
 
 th.start();
 }

 /**
 * 测试可见性,可见性依赖于委托机制
 */
 private static void test2() {
 
 /**
 * 1 让ExtClassLoader加载 jg.zhang.java.testConcurrent.Person这个类
 * 因为这个类不在jre/lib/ext目录下或者java.ext.dirs系统属性定义的目录下
 * 所以抛出ClassNotFoundException
 * 
 * 所以父加载器不能加载应该被子加载器加载的类,这个类在父加载器中不可见
 * 这种机制依赖于委派机制
 */
 
 try {
 Class.forName("jg.zhang.java.testConcurrent.Person", true, 
  ClassLoaderTest.class.getClassLoader().getParent());
 System.out.println("1 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("1 -- 未找到类");
 }
 
 
 /**
 * 2 让AppClassLoader加载java.lang.String类
 * 没有抛出异常,说明类被正常加载了
 * 虽然是由AppClassLoader一直委派到BootStrap而加载的
 * 所以可以说,父加载器加载的类对于子加载器来说是可见的,这同样依赖于委派机制
 * 
 * 其实在虚拟机启动初期,java.lang.String已经被BootStrap预加载了
 * 这时再次加载,虚拟机发现已经加载,不会再重复加载
 */
 try {
 Class.forName("java.lang.String", true, 
  ClassLoaderTest.class.getClassLoader());
 System.out.println("2 -- 类被加载");
 } catch (ClassNotFoundException e) {
 //e.printStackTrace();
 System.out.println("2 -- 未找到类");
 }
 
 }

 /**
 * 验证三种类加载器的父子关系
 */
 private static void test1() {
 ClassLoader appClassLoader = ClassLoaderTest.class.getClassLoader();
 System.out.println(appClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 
 ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
 System.out.println(sysClassLoader); //sun.misc.Launcher$AppClassLoader@19821f
 //由上面的验证可知, 应用程序类加载器和系统类加载器是相同的, 因为地址是一样的
 
 ClassLoader extClassLoader = appClassLoader.getParent();
 System.out.println(extClassLoader); //sun.misc.Launcher$ExtClassLoader@addbf1
 //AppClassLoader的父加载器是ExtClassLoader
 
 System.out.println(extClassLoader.getParent()); //null
 //ExtClassLoader的父加载器是null, 也就是BootStrap,这是由c语言实现的

 }

}

Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung des Klassenladecodes in Java. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

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