この記事では、主に Java のクラスローダーの関連情報を詳しく紹介します。興味のある方は参考にしてください
Java の動的性質からクラスローディングメカニズムまで
Java は動的です。言語。では、この「ダイナミック」をどうやって理解すればよいのでしょうか?言い換えれば、言語はどのような特徴を持っていれば動的言語と呼ばれるのでしょうか? Javaの場合、これが私がそれを理解する方法です。
JVM (Java 仮想マシン) は、ローカルのマシンコード命令を実行しませんが、バイトコードと呼ばれるタイプの命令 (クラス ファイルに存在します) を実行します。これには、実際にバイトコードを実行する前に、仮想マシンが関連するクラス ファイルをメモリにロードする必要があります。仮想マシンは、実行時に将来どのクラス ファイルが使用されるかわからないため、必要なすべてのクラス ファイルを一度にロードしません。クラスが使用されるたびに、このクラスに関連するクラス ファイルが実行時に「動的」にロードされます。これが、Java が動的言語と呼ばれる根本的な理由です。クラスを動的にロードするだけでなく、クラスも動的に初期化され、動的にリンクされます。動的初期化と動的リンクについては、他の記事で紹介されています。この記事ではクラスの読み込みについてのみ説明します。
この記事で紹介するクラスローダー(ClassLoader)は、JVMにクラスをロードする役割を担うため、クラスローダーはJVMに欠かせない重要なコンポーネントです。
Javaのクラスローダーとクラスローダーの動作原理
Javaには3つのクラスローダーがあります(javaseを参照)。各クラスローダーは作成時に対応するディレクトリを指定しているので、各クラスローダーがクラスをロードする場所が決定されるということは、ClassLoaderクラスにgetTargetPath()などのメソッドがあるはずだと思います。 jdk ドキュメントを調べたところ、それらは利用できないことがわかりました。これら 3 つのクラス ローダーとそれらに対応するパスは次のとおりです:
* AppClassLoader -- クラスパスで指定されたパスにクラスをロードします
* ExtClassLoader -- jre/lib/ext ディレクトリまたは java.ext.dirs システムに定義されているクラスをロードしますproperty ディレクトリ内のクラス
* BootStrapそれでは、クラスローダーはどのように機能しますか? ? jdk内のClassLoaderクラスのソースコードを参照できます。このクラスの実装では、まず、loadClass メソッドがクラスをロードします。このメソッドは、クラス ファイルのデータを読み取って返します。その後、loadClass メソッドが続行されます。返されたデータは、仮想マシンの実行時に認識できる型情報に処理されます。したがって、独自のクラス ローダーを開発する場合は、jdk で ClassLoader クラスを継承し、findClass メソッドをオーバーライドするだけで済みます。残りの作業は親クラスが行います。他の Java プラットフォームには、Javaee プラットフォームの Tomcat サーバーなど、独自のニーズに応じて独自のクラス ローダーが実装されており、Android プラットフォームの Dalvik 仮想マシンも独自のクラス ローダーを定義しています。
仮想マシンがクラスをロードするには 2 つの方法があります。1 つは前述の ClassLoader.loadClass() メソッドで、もう 1 つはリフレクション API の Class.forName() メソッドを使用する方法です。 Class.forName() メソッドも ClassLoader として使用されます。 Class クラスの for
Name メソッド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;
クラス ローダーには、委譲、可視性、および単一性という 3 つの特性があり、これらについて説明します。これら 3 つの機能の紹介は次のとおりです:
* 委任メカニズムとは、クラスをロードするリクエストを親クラス ローダーに渡すことを指します。親クラス ローダーがクラスを見つけられない場合、またはクラスをロードできない場合は、クラスを再度ロードします。 。
* 可視性の原則は、子クラスローダーは親クラスローダーによってロードされたすべてのクラスを見ることができますが、親クラスローダーは子クラスローダーによってロードされたクラスを見ることができないということです。 * 統一の原則は、クラスが 1 回だけロードされることを意味します。これは、委任メカニズムにより、親クラス ローダーによってロードされたクラスが子クラス ローダーによって再度ロードされないことが保証されるためです。
その中で、委任メカニズムは、他の資料では、クラスローダーの親委任モデルとも呼ばれます。実際には、同じ意味です。加法性と統一性は委任メカニズムに依存します。
次のコードは、クラス ローダーの委任メカニズムをテストします:
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语言实现的
系统类加载器和线程上下文类加载器
在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语言实现的 } }
以上がJavaのクラスローダーコードの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。