JVM classloader

JVM启动,会形成3个类加载器组成的初始化加载器层次结构:
bootstap classloader (加载核心类)
        ||
extension classloader(加载ext(目录),即java.ext.dirs()  \jre\lib\ext
        ||
system classloader   (加载-classpath或者java.class.path或者CLASSPATH)

对于每一个Java进程都有一个System Class Loader,也叫Application Class Loader,用于加载写在CLASSPATH中的Class,并作为其它用户Class Loader的缺省的Parent Class Loader。由System Class Loader加载,System Class Loader的Parent是Ext. Class Loader。Ext. Class Loader还具有一个Parent Class Loader,就是BootstrapClassLoader。该Class Loader是Java VM内置的Class Loader,用于加载java.*。如果使用ClassLoader.getParent()方法,当一个Class Loader的Parent是Bootstrap是,一般返回null。(Windows, Linux, Solaris均如此)
ClassLoader机制:
a)全盘负责:一个classloader加载一个class后,这个class所引用或者依赖的类也由这个classloader载入,除非显式的用另一个classloader载入,但是java核心类不受影响,无论怎么一直是用bootstap classloader加载的。
b)委托机制:先由父加载器加载,除非父加载器找不到时才从自己的类路径中去寻找
c)Cache机制:classloader采用缓存机制,即先查cache;若cache中保存了这个class就直接返回;若无,才从文件读取和转化为class并放入cache

ClassLoader加载类顺序:
检查cache是否有该类:
    若有直接返回    若无,请求父类加载  若无父,则从bootstap classloader加载
加载:
    寻找class文件(丛与此classloader相关的类路径中寻找) 从文件载入class        找不到则抛出ClassNotFoundeException
扩展:
    加载时即2),覆写findClass可以实现自己的载入策略
    加载时即2),覆写loadClass来实现自己的载入过程(更高级别)
如何实现运行时动态载入与更新?
本质:只要动态改类搜索路径和清除classloader的cache已载入的class就ok
做法:
1)继承ClassLoader:覆写loadClass方法(不是findclass),动态寻找class文件
2)只要重新使用一个新的类搜索路径来new一个classloader就可以,这样既更新了类的搜索路径以便来载入新的class,也更新生成了一个空白的cache
Thread 的Context Class Loader
自从JDK1.2以后,Sun 为java.lang.thread加入了setContextClassLoader和getContextClassLoader两个方法,但是并没有指明应该如何使用。通常比较Confuse的是,当执行下述代码时,是从Thread的Context Class Loader中加载Class A,还是从加载Class B的Class Loader中加载A(如果两个ClassLoader不同的话)?
public class B {
public void method1() {    
A a = new A();              ... }}
public class B {   
public void method1() {
Class clazz = Class.forName("A");      
Object o = clazz.newInstance();     ... }}
答案是从加载B的Class Loader中加载A,而不是从Thread的Context Class Loader中加载A。那Context Class Loader是做什么用的呢?举例来说:在JDK1.4中,加入了XML的支持(JAXP),用户可以指定DocumentBuilder的Concrete Class,该Class需实现JAXP接口。在DocumentBuilderFactory中是使用Class.forName加载该Concrete Class的。DocumentBuilderFactory是使用Bootstrap Class Loader来加载的,但通常JAXP的实现都是位于CLASSPATH中,是由System Class Loader加载,而Bootstrap Class Loader又是System Class Loader的Ancestor Class Loader,即Bootstrap Class Loader不能从System Class Loader中加载Class。这时,就可以显式的使用Thread.getContextClassLoader来解决这一问题。而在JDK1.4中也正式这样实现的。
在Java进程启动时,main thread的Context Class Loader即为System Class Loader。
注意点:
l         委托机制:先由父加载器加载,除非父加载器找不到时才从自己的类路径中去寻找     这里的父加载器需要显示地指定,不是说一个类继承了另一个类,另一个类就自动成为了它的父加载器,在自定义类加载器时,如果你不指定父加载器时,系统会以system classloader作为它的父加载器。
l         全盘负责:一个classloader加载一个class后,这个class所引用或者依赖的类也由这个classloader载入,除非显式的用另一个classloader载入
l         接口也是从类加载器中加载的
l         特殊说明
特殊说明1:如果没有特殊指定,用户自定义的classloader都把system classloader作为它的父加载器
特殊说明2:jvm认为不同的classloade载入相同名字的class是不同的,即使从同一个class文件载入
l         针对上面的特殊说明2
将接口或者基类放入classpath               <---------system classloader
执行时,动态载入实现或者继承这些接口或者基类的子类;<-------customized classloader
用customized classloader载入类时,发现它有一个父类class(extends);但是在载入它时,jvm先加载父类class; 这个父类是system classloader能识别的; 根据“委托机制”它将由system classloader来加载;然后customized classloader(实际是system classloader来加载)再载入这个class,创建一个实例,转型为父类; jvm就使用system classloader再次载入父类class,然后将此实例转型为这个父类class;
这个过程加载了两个父类class,都是由system classloader载入;即同一个classloader载入同一个文件,造型不会由异常
l         不同classLoader加载的类不能够进行类型转换,即使它们存在继承和实现关系。
AnotherClassLoader loader = new AnotherClassLoader(null, "c:/");
              Class a = loader.loadClass("example.classloader.A");
              //a.newInstance();
//用来验证不同classLoadr加载的类不能进行强类型转换,Father instanceA中的father是系统加载类器加载的,a.newInstance()返回的
              //类是用自定义加载器加载的
              Father instanceA = (Father)a.newInstance();
这段代码将会报错,虽然A是Father的子类
l         自定义类加载器时,如果将你父加载器设置为空,则extension classloader  和system ClassLoader将不会得到调用,但是bootstap classloader (加载核心类)却能得到调用。就是说java核心类无论如何都不会加载不到。参考JDK中的ClassLoader的逻辑:
Class c = findLoadedClass(name);
       if (c == null) {
           try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClass0(name);
              }
           } catch (ClassNotFoundException e) {
               // If still not found, then invoke findClass in order
               // to find the class.
               c = findClass(name);
           }
       }


ClassLoader

--------------------------------------------------------------------------------

null(Bootstrap ClassLoader)
          |
sun.misc.Launcher$ExtClassLoader
           |                              |
sun.misc.Launcher$AppClassLoader  java.net.URLClassLoader
           |
 
JVM本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class(即所有java.*开头的类,加载JRE/lib下的rt.jar和其他重要jar文件)。
另外JVM还会提供两个ClassLoader,它们都是用Java语言编写的,由Bootstrap ClassLoader加载;其中Extension ClassLoader负责加载扩展的Java class(例如所有javax.*开头的类和存放在JRE的ext目录下的类,加载JRE/lib/ext下的jar文件,Java扩展框架使用),Application ClassLoader负责加载应用程序自身的类。
 
因为JVM自带的ClassLoader只是懂得从本地文件系统加载标准的java class文件,如果编写你自己的ClassLoader,你可以做到:
1)在执行非置信代码之前,自动验证数字签名
2)动态地创建符合用户特定需要的定制化构建类
3)从特定的场所取得java class,例如数据库中,网络等
 
当你决定创建你自己的ClassLoader时,需要继承java.lang.ClassLoader或者它的子类。在实例化每个ClassLoader对象时,需要指定一个父对象;如果没有指定的话,系统自动指定ClassLoader.getSystemClassLoader()为父对象
在Java 1.2后,java class的加载采用所谓的委托模式(Delegation Modle),当调用一个ClassLoader.loadClass()加载一个类的时候,将遵循以下的步骤:
1)检查这个类是否已经被加载进来了?
2)如果还没有加载,调用父对象加载该类
3)如果父对象无法加载,调用本对象的findClass()取得这个类。
所以当创建自己的Class Loader时,只需要重载findClass()这个方法。
在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded."
事实上我们关心的不是如何卸载类的,我们关心的是如何更新已经被加载了的类从而更新应用的功能。JSP则是一个非常典型的例子,如果一个JSP文件被更改了,应用服务器则需要把更改后的JSP重新编译,然后加载新生成的类来响应后继的请求。
其实一个已经加载的类是无法被更新的,如果你试图用同一个ClassLoader再次加载同一个类,就会得到异常(java.lang.LinkageError: duplicate class definition),我们只能够重新创建一个新的ClassLoader实例来再次加载新类。至于原来已经加载的类,开发人员不必去管它,因为它可能还有实例正在被使用,只要相关的实例都被内存回收了,那么JVM就会在适当的时候把不会再使用的类卸载。
 
 装载 连接及初始化
在一个Java类的生命周期中,装载,连接和初始化只是其开始阶段.只有开始阶段结束以后,类才可以被实例化并被使用.整个开始阶段必须按以下顺序进行:
1)装载   把二进制形式的Java class读入虚拟机中.
2)连接   把已经读入虚拟机的二进制形式的类数据合并到虚拟机的运行状态中去.连接阶段分为验证,准备和解析三个子步骤.
3)初始化   给类变量赋以适当的初始值.
Java虚拟机允许类装载器(启动或用户自定义类装载器)缓存Java class的二进制形式,在预知某个类将要被使用时就装载它.如果一个类装载器在预先装载时遇到问题,它应该在该类被"首次主动使用"时报告该问题(通过抛出一个java.lang.LinkageError的子类).也就是说,如果一个类装载器在预先装载时遇到缺失或错误的class文件,它必须等到程序首次被主动使用该类时才报告错误.如果这个类一直没有被程序主动使用,那么该类装载器将不会报告错误.

命名空间  
Java虚拟机为每一个类装载器维护一个唯一标识的命名空间.一个Java程序可以多次装载具有同一个全限定名(指类所属的包名加类名,如java.lang.Object就是类Object的全限定名)的多个类(class). Java虚拟机要确定这"多个类"的唯一性,因此,当多个类装载器都装载了同名的类时,为了唯一地标识这个类,还要在类名前加上装载该类的类装载器的标识(指出了类所位于的命名空间).例如:ExtClassLoader 装载了sun.text.resources.DateFormatZoneData_zh_CN类,AppClassLoader装载了sun.text.resources.DateFormatZoneData_zh_HK类, Java虚拟机就认为这两个类位于不同的包中,彼此之间不能访问私有成员.如果AppClassLoader也装载了sun.text.resources.DateFormatZoneData_zh_CN类, 虽然"类名"相同,Java虚拟机也认为它们是不同的类,因为它们处在不同的命名空间中.
JVM启动,会形成3个类加载器组成的初始化加载器层次结构:
bootstap classloader (加载核心类)
        ||
extension classloader(加载ext(目录),即java.ext.dirs()  \jre\lib\ext
        ||
system classloader   (加载-classpath或者java.class.path或者CLASSPATH)

对于每一个Java进程都有一个System Class Loader,也叫Application Class Loader,用于加载写在CLASSPATH中的Class,并作为其它用户Class Loader的缺省的Parent Class Loader。由System Class Loader加载,System Class Loader的Parent是Ext. Class Loader。Ext. Class Loader还具有一个Parent Class Loader,就是BootstrapClassLoader。该Class Loader是Java VM内置的Class Loader,用于加载java.*。如果使用ClassLoader.getParent()方法,当一个Class Loader的Parent是Bootstrap是,一般返回null。(Windows, Linux, Solaris均如此)
ClassLoader机制:
a)全盘负责:一个classloader加载一个class后,这个class所引用或者依赖的类也由这个classloader载入,除非显式的用另一个classloader载入,但是java核心类不受影响,无论怎么一直是用bootstap classloader加载的。
b)委托机制:先由父加载器加载,除非父加载器找不到时才从自己的类路径中去寻找
c)Cache机制:classloader采用缓存机制,即先查cache;若cache中保存了这个class就直接返回;若无,才从文件读取和转化为class并放入cache

ClassLoader加载类顺序:
检查cache是否有该类:
    若有直接返回    若无,请求父类加载  若无父,则从bootstap classloader加载
加载:
    寻找class文件(丛与此classloader相关的类路径中寻找) 从文件载入class        找不到则抛出ClassNotFoundeException
扩展:
    加载时即2),覆写findClass可以实现自己的载入策略
    加载时即2),覆写loadClass来实现自己的载入过程(更高级别)
如何实现运行时动态载入与更新?
本质:只要动态改类搜索路径和清除classloader的cache已载入的class就ok
做法:
1)继承ClassLoader:覆写loadClass方法(不是findclass),动态寻找class文件
2)只要重新使用一个新的类搜索路径来new一个classloader就可以,这样既更新了类的搜索路径以便来载入新的class,也更新生成了一个空白的cache
Thread 的Context Class Loader
自从JDK1.2以后,Sun 为java.lang.thread加入了setContextClassLoader和getContextClassLoader两个方法,但是并没有指明应该如何使用。通常比较Confuse的是,当执行下述代码时,是从Thread的Context Class Loader中加载Class A,还是从加载Class B的Class Loader中加载A(如果两个ClassLoader不同的话)?
public class B {
public void method1() {    
A a = new A();              ... }}
public class B {   
public void method1() {
Class clazz = Class.forName("A");      
Object o = clazz.newInstance();     ... }}
答案是从加载B的Class Loader中加载A,而不是从Thread的Context Class Loader中加载A。那Context Class Loader是做什么用的呢?举例来说:在JDK1.4中,加入了XML的支持(JAXP),用户可以指定DocumentBuilder的Concrete Class,该Class需实现JAXP接口。在DocumentBuilderFactory中是使用Class.forName加载该Concrete Class的。DocumentBuilderFactory是使用Bootstrap Class Loader来加载的,但通常JAXP的实现都是位于CLASSPATH中,是由System Class Loader加载,而Bootstrap Class Loader又是System Class Loader的Ancestor Class Loader,即Bootstrap Class Loader不能从System Class Loader中加载Class。这时,就可以显式的使用Thread.getContextClassLoader来解决这一问题。而在JDK1.4中也正式这样实现的。
在Java进程启动时,main thread的Context Class Loader即为System Class Loader。
注意点:
l         委托机制:先由父加载器加载,除非父加载器找不到时才从自己的类路径中去寻找     这里的父加载器需要显示地指定,不是说一个类继承了另一个类,另一个类就自动成为了它的父加载器,在自定义类加载器时,如果你不指定父加载器时,系统会以system classloader作为它的父加载器。
l         全盘负责:一个classloader加载一个class后,这个class所引用或者依赖的类也由这个classloader载入,除非显式的用另一个classloader载入
l         接口也是从类加载器中加载的
l         特殊说明
特殊说明1:如果没有特殊指定,用户自定义的classloader都把system classloader作为它的父加载器
特殊说明2:jvm认为不同的classloade载入相同名字的class是不同的,即使从同一个class文件载入
l         针对上面的特殊说明2
将接口或者基类放入classpath               <---------system classloader
执行时,动态载入实现或者继承这些接口或者基类的子类;<-------customized classloader
用customized classloader载入类时,发现它有一个父类class(extends);但是在载入它时,jvm先加载父类class; 这个父类是system classloader能识别的; 根据“委托机制”它将由system classloader来加载;然后customized classloader(实际是system classloader来加载)再载入这个class,创建一个实例,转型为父类; jvm就使用system classloader再次载入父类class,然后将此实例转型为这个父类class;
这个过程加载了两个父类class,都是由system classloader载入;即同一个classloader载入同一个文件,造型不会由异常
l         不同classLoader加载的类不能够进行类型转换,即使它们存在继承和实现关系。
AnotherClassLoader loader = new AnotherClassLoader(null, "c:/");
              Class a = loader.loadClass("example.classloader.A");
              //a.newInstance();
//用来验证不同classLoadr加载的类不能进行强类型转换,Father instanceA中的father是系统加载类器加载的,a.newInstance()返回的
              //类是用自定义加载器加载的
              Father instanceA = (Father)a.newInstance();
这段代码将会报错,虽然A是Father的子类
l         自定义类加载器时,如果将你父加载器设置为空,则extension classloader  和system ClassLoader将不会得到调用,但是bootstap classloader (加载核心类)却能得到调用。就是说java核心类无论如何都不会加载不到。参考JDK中的ClassLoader的逻辑:
Class c = findLoadedClass(name);
       if (c == null) {
           try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClass0(name);
              }
           } catch (ClassNotFoundException e) {
               // If still not found, then invoke findClass in order
               // to find the class.
               c = findClass(name);
           }
       }
 
 

你可能感兴趣的:(ClassLoader)