>  기사  >  Java  >  Java의 클래스 로더 코드에 대한 자세한 설명

Java의 클래스 로더 코드에 대한 자세한 설명

黄舟
黄舟원래의
2017-06-18 09:44:271526검색

이 글은 주로 Java의 클래스 로더 관련 정보를 자세히 소개하며, 이는 특정 참조 가치가 있습니다. 관심 있는 친구들은 이를 참고할 수 있습니다.

Java의 동적 특성에서 클래스 로딩 메커니즘까지

Java는 동적입니다. 언어. 그렇다면 이 "동적"을 어떻게 이해해야 할까요? 즉, 언어가 동적 언어라고 부르려면 어떤 특성을 갖추어야 할까요? Java의 경우 이것이 내가 이해하는 방법입니다.

JVM(Java Virtual Machine)은 로컬 머신 코드 명령어를 실행하지 않고 바이트코드(클래스 파일에 존재)라는 일종의 명령어를 실행합니다. 이를 위해서는 실제로 바이트코드를 실행하기 전에 가상 머신이 관련 클래스 파일을 메모리에 로드해야 합니다. 가상머신은 실행 시 나중에 어떤 클래스 파일이 사용될지 모르기 때문에 필요한 모든 클래스 파일을 한 번에 로드하지 않습니다. 클래스가 사용될 때마다 이 클래스와 관련된 클래스 파일은 런타임 시 "동적으로" 로드됩니다. 이것이 Java가 동적 언어라고 불리는 근본적인 이유입니다. 클래스를 동적으로 로드하는 것 외에도 클래스는 동적으로 초기화되고 동적으로 연결됩니다. 동적 초기화 및 동적 연결은 다른 문서에 소개되어 있습니다. 이 문서에서는 클래스 로딩에만 관심이 있습니다.

이 글에서 소개할 클래스 로더(ClassLoader)는 JVM에서 클래스를 로딩하는 역할을 담당합니다. 따라서 클래스 로더는 JVM에서 없어서는 안 될 중요한 구성 요소입니다.

Java의 클래스 로더와 클래스 로더 작동 원리

Java에는 세 가지 클래스 로더가 있습니다(javase를 지칭함). 각 클래스 로더는 생성될 때 해당 디렉토리를 지정했는데, 이는 각 클래스 로더가 클래스를 로드하기 위해 어디로 가는지 결정된다는 의미입니다. GetTargetPath()와 같은 메소드가 있어야 해당 경로를 찾을 수 있습니다. jdk 문서에서 사용할 수 없다는 것을 발견했습니다. 다음은 세 가지 클래스 로더와 해당 경로입니다.

* AppClassLoader -- 클래스 경로로 지정된 경로에 클래스를 로드합니다.
* ExtClassLoader -- jre/lib/ext 디렉토리 또는 java.ext.dirs 시스템에 정의된 클래스를 로드합니다. 디렉터리의 속성 클래스
* BootStrap                                                                                    > jdk에서 ClassLoader 클래스의 소스코드를 참조할 수 있습니다. 이 클래스의 구현에서는 먼저 loadClass 메서드가 클래스를 로드합니다. 이 메서드는 findClass 메서드를 읽고 반환한 후 loadClass 메서드를 계속합니다. DefineClass 메소드를 호출하면 반환된 데이터는 가상 머신 실행 시 인식할 수 있는 유형 정보로 처리됩니다. 따라서 자체 클래스 로더를 개발하는 경우 jdk에서 ClassLoader 클래스만 상속하고 findClass 메서드를 재정의하면 나머지는 상위 클래스가 수행합니다. 일부 다른 Java 플랫폼은 Javaee 플랫폼의 tomcat 서버와 같이 자체 필요에 따라 자체 특정 클래스 로더를 구현했으며 Android 플랫폼의 Dalvik 가상 머신도 자체 클래스 로더를 정의합니다.

가상 머신이 클래스를 로드하는 방법에는 두 가지가 있습니다. 하나는 위에서 언급한 ClassLoader.loadClass() 메서드이고, 다른 하나는 리플렉션 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;

클래스 로더의 세 가지 특성

클래스 로더에는 위임, 가시성 및 단일성이라는 세 가지 특성이 있으며 이에 대해 논의합니다. 다른 글에서 이 세 가지 기능에 대한 소개는 다음과 같습니다.

* 위임 메커니즘은 상위 클래스 로더에 클래스 로드 요청을 전달하는 것을 의미합니다. 상위 클래스 로더가 해당 클래스를 찾거나 로드할 수 없으면 다시 로드합니다. .

* 가시성 원칙은 하위 클래스 로더가 상위 클래스 로더가 로드한 모든 클래스를 볼 수 있지만 상위 클래스 로더는 하위 클래스 로더가 로드한 클래스를 볼 수 없다는 것입니다.

* 단일성 원칙은 클래스가 한 번만 로드된다는 것을 의미합니다. 이는 위임 메커니즘이 하위 클래스 로더가 상위 클래스 로더에 의해 로드된 클래스를 다시 로드하지 않도록 보장하기 때문입니다.

그중 위임 메커니즘이 기초입니다. 다른 자료에서는 이 메커니즘을 클래스 로더의 상위 위임 모델이라고도 합니다. 실제로는 같은 의미입니다. 추가성과 통일성은 위임 메커니즘에 따라 달라집니다.


다음 코드는 클래스 로더의 위임 메커니즘을 테스트합니다.

 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语言实现的

인쇄 결과를 보면 우리가 작성한 클래스를 로드하는 로더가 AppClassLoader이고, AppClassLoader의 상위 로더가 ExtClassLoader이며, ExtClassLoader의 상위 로더 로더의 반환 결과는 null입니다. 이는 추가 로더가 BootStrap임을 의미합니다. 이 로더는 가상 머신이 시작되면 jdk의 클래스가 로드됩니다. C로 구현되었으며 해당 Java 객체가 없으므로 null이 반환됩니다. 그러나 논리적으로 BootStrap은 여전히 ​​ExtClassLoader의 상위 로더입니다. 즉, ExtClassLoader가 클래스를 로드할 때마다 로드를 위해 항상 BootStrap에 위임됩니다.

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

在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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.