`

笔记]类加载器

阅读更多

http://www.iteye.com/topic/68170

 

看了王森的《JAVA深度历险》,做了一个简单的学习笔记。
1. JAVA类装载器在装载类的时候是按需加载的,只有当一个类要使用(使用new 关键字来实例化一个类)的时候,类加载器才会加载这    个类并初始化。
    类Main:
   

java 代码
 
  1. public class Main {  
  2.     public static void main(String[] args) {  
  3.         A a = new A();  
  4.         a.print();  
  5.         B b = new B();  
  6.         b.print();  
  7.     }  
  8. }  

  
  类A:

java 代码
 
  1. public class A  {  
  2.     public void print() {  
  3.         System.out.println("Using Class A");  
  4.     }  
  5. }  


 类B:

java 代码
 
  1. public class B {  
  2.     public void print() {  
  3.         System.out.println("Using Class B");  
  4.     }  
  5. }  


执行:java -varbose:class Main
执行结果:
    E:\DEV>java -verbose:class Main
    [Opened C:\Program Files\Java\jre1.5.0_11\lib\rt.jar] (类装载器会首先加载rt.jar加载基础类)
    .
    .
    [Loaded Main from file:/E:/DEV/] (类装载器载入相应类并初始化)
    [Loaded A from file:/E:/DEV/]
    Using Class A
    [Loaded B from file:/E:/DEV/]
    Using Class B
2. 让JAVA程序具有动态性
   使用显式方式来实现动态性,我们需要自己动手处理类载入时的细节部分。

     两种方法:
    |
    +-- 隐式的 : 使用new关键字让类加载器按需求载入所需的类
    |
    +-- 显式的 :
                     |
                     +-- 由 java.lang.Class的forName()方法加载
                     |
                     +-- 由 java.lang.ClassLoader的loadClass()方法加载

    (1) 使用Class.forName()
    Class.forName()方法具有两个重载的方法:
            +- public static Class forName(String className)
            |
            +- public static Class forName(String className, boolean initialize,ClassLoader loader)
    参数说明:
        className - 所需类的完全限定名
        initialize - 是否必须初始化类(静态代码块的初始化)
        loader - 用于加载类的类加载器
调用只有一个参数的forName()方法等效于 Class.forName(className, true, loader)。
这两个方法,最后都要连接到原生方法forName0(),其定义如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;
只有一个参数的forName()方法,最后调用的是:
forName0(className, true, ClassLoader.getCallerClassLoader());
而三个参数的forName(),最后调用的是:
forName0(name, initialize, loader);
所以,不管使用的是new 來实例化某个类、或是使用只有一个参数的Class.forName()方法,内部都隐含了“载入类 + 运行静态代码块”的步骤。而使用具有三个参数的Class.forName()方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块,只有当实例化这个类的时候,静态代码块才会被初始化,静态代码块是在类第一次实例化的时候才初始化的。

    (2) 直接使用类加载器
           +— 获得对象所属的类 : getClass()方法
           |
           +— 获得该类的类加载器 : getClassLoader()方法

java 代码
 
  1. public class Main3 {  
  2.     public static void main(String[] args) throws Exception {  
  3.         Main3 main3 = new Main3();  
  4.         System.out.println("准备载入类");  
  5.         ClassLoader loader = main3.getClass().getClassLoader();  
  6.         Class clazzA = loader.loadClass(args[0]);  
  7.         System.out.println("实例化类A");  
  8.         A o1 = (A) clazzA.newInstance();          
  9.     }  
  10. }  


3  类加载器的层次
                                          

 

 

 

 

 

 

 

我们可以通过java.net.URLClassLoader这个类构建一个自己的类加载器,来载入自己所需要的类。
下面是JDK中的说明:

public class URLClassLoader
extends SecureClassLoader
该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。

当启动JVM的时候,可以使用三个类加载器:引导(bootstrap)类加载器、扩展(extensions)类加载器、应用程序(application)类加载器。

1.引导类加载器仅仅负责加载核心的Java库,比如位于<JAVA_HOME>/jre/lib 目录下的vm.jar,core.jar。这个类加载器,是JVM核心部分,是用native代码写成的。

2. 扩展类加载器负责加载扩展路径下的代码,一般位于<JAVA_HOME>/jre/lib/ext  或者通过java.ext.dirs 这个系统属性指定的路径下的代码。这个类加载器是由sun.misc.Launcher$ExtClassLoader 实现的。

3.应用程序类加载器负责加载java.class.path(映射系统参数 CLASSPATH的值) 路径下面的代码,这个类加载器是由 sun.misc.Launcher$AppClassLoader 实现的。

String bootClassPath = System.getProperty("sun.boot.class.path");

String extClassPath = System.getProperty("java.ext.dirs");

String appClassPath = System.getProperty("java.class.path");
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/lihe2008125/archive/2009/02/05/3864983.aspx

开发者通常会使用如下语法通过类加载器机制加载属性文件:

Properties p = new Properties();

p.load(MyClass.class.getClassLoader().getResourceAsStream("myApp.properties"

));

这个意思是:如果MyClass 由扩展类加载器加载,而 myApp.properties 文件只能应用程序类加载器看到,则装入属性文件就会失败。

=========================

jvm classLoader architecture :

a, Bootstrap ClassLoader/启动类加载器
       主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包装入工作.

b, Extension ClassLoader/扩展类加载器
       主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作

c, System ClassLoader/系统类加载器
        主要负责java -classpath/-Djava.class.path所指的目录下的类与jar包装入工作.

b, User Custom ClassLoader/用户自定义类加载器(java.lang.ClassLoader的子类)
        在程序运行期间, 通过java.lang.ClassLoader的子类动态加载class文件, 体现java动态实时类装入特性.

类加载器的特性:

1, 每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类。
2, 为了实现java安全沙箱模型顶层的类加载器安全机制, java默认采用了 " 双亲委派的加载链 " 结构.
如下图:

 

Class Diagram:


类图中, BootstrapClassLoader是一个单独的java类, 其实在这里, 不应该叫他是一个java类。
因为, 它已经完全不用java实现了。

它是在jvm启动时, 就被构造起来的, 负责java平台核心库。(具体上面已经有介绍)

启动类加载实现 (其实我们不用关心这块, 但是有兴趣的, 可以研究一下 ):

bootstrap classLoader 类加载原理探索
www.iteye.com/topic/136885

自定义类加载器加载一个类的步骤 :

 

 

 


ClassLoader 类加载逻辑分析, 以下逻辑是除 BootstrapClassLoader 外的类加载器加载流程:

java 代码
  1. // 检查类是否已被装载过
  2. Class c = findLoadedClass(name);
  3. if (c == null ) {
  4.      // 指定类未被装载过
  5.      try {
  6.          if (parent != null ) {
  7.              // 如果父类加载器不为空, 则委派给父类加载
  8.              c = parent.loadClass(name, false );
  9.          } else {
  10.              // 如果父类加载器为空, 则委派给启动类加载加载
  11.              c = findBootstrapClass0(name);
  12.          }
  13.      } catch (ClassNotFoundException e) {
  14.          // 启动类加载器或父类加载器抛出异常后, 当前类加载器将其
  15.          // 捕获, 并通过findClass方法, 由自身加载
  16.          c = findClass(name);
  17.      }
  18. }

用Class.forName加载类
Class.forName使用的是被调用者的类加载器来加载类的.
这种特性, 证明了java类加载器中的名称空间是唯一的, 不会相互干扰.
即在一般情况下, 保证同一个类中所关联的其他类都是由当前类的类加载器所加载的.

java 代码
  1. public static Class forName(String className)
  2.      throws ClassNotFoundException {
  3.      return forName0(className, true , ClassLoader.getCallerClassLoader());
  4. }

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


上图中 ClassLoader.getCallerClassLoader 就是得到调用当前forName方法的类的类加载器


线程上下文类加载器
java默认的线程上下文类加载器是 系统类加载器(AppClassLoader).

java 代码
  1. // Now create the class loader to use to launch the application
  2. try {
  3.     loader = AppClassLoader.getAppClassLoader(extcl);
  4. } catch (IOException e) {
  5.     throw new InternalError(
  6. "Could not create application class loader" );
  7. }

  8. // Also set the context class loader for the primordial thread.
  9. Thread.currentThread().setContextClassLoader(loader);


以上代码摘自sun.misc.Launch的无参构造函数Launch()。

使用线程上下文类加载器, 可以在执行线程中, 抛弃双亲委派加载链模式, 使用线程上下文里的类加载器加载类.
典型的例子有, 通过线程上下文来加载第三方库jndi实现, 而不依赖于双亲委派.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。
还有一些采用 hotswap 特性的框架, 也使用了线程上下文类加载器, 比如 seasar (full stack framework in japenese).

线程上下文从根本解决了一般应用不能违背双亲委派模式的问题.
使java类加载体系显得更灵活.

随着多核时代的来临, 相信多线程开发将会越来越多地进入程序员的实际编码过程中. 因此,
在编写基础设施时, 通过使用线程上下文来加载类, 应该是一个很好的选择.

当然, 好东西都有利弊. 使用线程上下文加载类, 也要注意, 保证多根需要通信的线程间的类加载器应该是同一个,
防止因为不同的类加载器, 导致类型转换异常(ClassCastException).

自定义的类加载器实现
defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)
是java.lang.Classloader提供给开发人员, 用来自定义加载class的接口.

使用该接口, 可以动态的加载class文件.

例如,
在jdk中, URLClassLoader是配合findClass方法来使用defineClass, 可以从网络或硬盘上加载class.

而使用类加载接口, 并加上自己的实现逻辑, 还可以定制出更多的高级特性.

比如,


一个简单的hot swap 类加载器实现:

java 代码
  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.lang.reflect.Method;
  4. import java.net.URL;
  5. import java.net.URLClassLoader;

  6. /**
  7. * 可以重新载入同名类的类加载器实现
  8. *

     

  9. * 放弃了双亲委派的加载链模式.
  10. * 需要外部维护重载后的类的成员变量状态.
  11. *
  12. * @author ken.wu
  13. * @mail ken.wug@gmail.com
  14. * 2007-9-28 下午01:37:43
  15. */
  16. public class HotSwapClassLoader extends URLClassLoader {

  17.     public HotSwapClassLoader(URL[] urls) {
  18.         super (urls);
  19.     }

  20.     public HotSwapClassLoader(URL[] urls, ClassLoader parent) {
  21.         super (urls, parent);
  22.     }

  23.     public Class load(String name)
  24.           throws ClassNotFoundException {
  25.         return load(name, false );
  26.     }

  27.     public Class load(String name, boolean resolve)
  28.           throws ClassNotFoundException {
  29.         if ( null != super .findLoadedClass(name))
  30.             return reload(name, resolve);

  31.         Class clazz = super .findClass(name);

  32.         if (resolve)
  33.             super .resolveClass(clazz);

  34.         return clazz;
  35.     }

  36.     public Class reload(String name, boolean resolve)
  37.           throws ClassNotFoundException {
  38.         return new HotSwapClassLoader( super .getURLs(), super .getParent()).load(
  39.             name, resolve);
  40.     }
  41. }

 

java 代码
  1. public class A {
  2.     private B b;

  3.     public void setB(B b) {
  4.          this .b = b;
  5.     }

  6.     public B getB() {
  7.          return b;
  8.     }
  9. }

 

java 代码
  1. public class B {}



这个类的作用是可以重新载入同名的类, 但是, 为了实现hotswap, 老的对象状态
需要通过其他方式拷贝到重载过的类生成的全新实例中来。(A类中的b实例)

而新实例所依赖的B类如果与老对象不是同一个类加载器加载的, 将会抛出类型转换异常(ClassCastException).

为了解决这种问题, HotSwapClassLoader自定义了load方法. 即当前类是由自身classLoader加载的, 而内部依赖的类还是老对象的classLoader加载的.

java 代码
  1. public class TestHotSwap {
  2. public static void main(String args[]) {
  3.     A a = new A();
  4.     B b = new B();
  5.     a.setB(b);

  6.     System.out.printf("A classLoader is %s \n" , a.getClass().getClassLoader());
  7.     System.out.printf("B classLoader is %s \n" , b.getClass().getClassLoader());
  8.     System.out.printf("A.b classLoader is %s \n" ,   a.getB().getClass().getClassLoader());

  9.     HotSwapClassLoader c1 = new HotSwapClassLoader( new URL[]{ new URL( "file:\\e:\\test\\")} , a.getClass().getClassLoader());
  10.     Class clazz = c1.load(" test.hotswap.A ");
  11.     Object aInstance = clazz.newInstance();

  12.     Method method1 = clazz.getMethod(" setB ", B.class);
  13.     method1.invoke(aInstance, b);

  14.     Method method2 = clazz.getMethod(" getB ", null);
  15.     Object bInstance = method2.invoke(aInstance, null);

  16.     System.out.printf(" reloaded A.b classLoader is %s \n", bInstance.getClass().getClassLoader());
  17. }
  18. }



输出

A classLoader is sun.misc.Launcher$AppClassLoader@19821f
B classLoader is sun.misc.Launcher$AppClassLoader@19821f
A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f
reloaded A.b classLoader is sun.misc.Launcher$AppClassLoader@19821f

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

    JVM
    jvm是jre里头一个动态连接函数库,jdk里面的jre一般用于运行java本身的程序,比如javac,等等.programfiles下面的jre用于运行用户编写的java程序.
    JRE下的bin\client 或者 bin\server 的jvm.dll就是JVM了

    当一台机器上有多个jvm可选择的时候,jvm的选择步骤:
    1)当前目录有没有jre目录(不准确),
    2)父目录下的jre子目录
    3)注册表HEKY_LOCAL_MACHINE\SoftWare\Java\Java Runtime Environment\
    所以当运行的是jdk\bin\java.exe的时候,用的jre是bin的父目录jdk下面的jre\
    运行java.exe找到了jre后有一个验证程序,验证jre和java.exe的版本是否一致,如果不一致则会发生错误

 

    java -verbose:class Main 显示调用的详细信息

 

    classloader的两种载入方式:1)pre-loading预先载入,载入基础类 2)load-on-demand按需求载入
    只有实例化一个类才会被classloader载入,仅仅申明并不会载入

 

    java动态载入class的两种方式:
    1)implicit隐式,即利用实例化才载入的特性来动态载入class
    2)explicit显式方式,又分两种方式:
      1)java.lang.Class的forName()方法
      2)java.lang.ClassLoader的loadClass()方法

 

    static块在什么时候执行?
    1)当调用forName(String)载入class时执行,如果调用ClassLoader.loadClass并不会执行.forName(String,false,ClassLoader)时也不会执行.
    2)如果载入Class时没有执行static块则在第一次实例化时执行.比如new ,Class.newInstance()操作
    3)static块仅执行一次

 


    Class类的实例.
    >>Class类无法手工实例化,当载入任意类的时候自动创建一个该类对应的Class的实例,
    >>某个类的所有实例内部都有一个栏位记录着该类对应的Class的实例的位置.,
    >>每个java类对应的Class实例可以当作是类在内存中的代理人.所以当要获得类的信息(如有哪些类变量,有哪些方法)时,都可以让类对应的Class的实例代劳.java的Reflection机制就大量的使用这种方法来实现
    >>每个java类都是由某个classLoader(ClassLoader的实例)来载入的,因此Class类别的实例中都会有栏位记录他的ClassLoader的实例,如果该栏位为null,则表示该类别是由bootstrap loader载入的(也称root laoder),bootstrap loader不是java所写成,所以没有实例.

    原生方法:forName0()等方法,native修饰符

 

    自定义ClassLoader:
    如实例化一个URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader优先找当前目录,再在url中找.class加载.URL中别忘在最后加"/"表示目录

 

    各个java类由哪些classLoader加载?
    1)java类可以通过实例.getClass.getClassLoader()得知
    2)接口由AppClassLoader(System ClassLoader,可以由ClassLoader.getSystemClassLoader()获得实例)载入
    3)ClassLoader类由bootstrap loader载入

 

    ClassLoader hierachy:
    jvm建立->初始化动作->产生第一个ClassLoader,即bootstrap loader->bootstrap loader在sum.misc.Launcher类里面的ExtClassLoader,并设定其Parent为null->bootstrap loader载入sun.misc.Launcher$AppClassLoader,并设定其parent为ExtClassLoader(但是AppClassLoader也是由bootstrap loader所载入的)->AppClassLoader载入各个xx.class,xx.class也有可能被ExtclassLoader或者bootstrap loader载入.
    >>自定义的ClassLoader的.getParent()是AppClassLoader.parent和他的加载器并没有关系
    >>ExtClassLoader和AppClassLoader都是URLClassLoader的子类.AppClassLoader的URL是由系统参数java.class.path取出的字符串决定,而java.class.path由 运行java.exe时 的-cp或-classpath或CLASSPATH环境变量决定
    >>ExtClassLoader查找的url是系统变量java.ext.dirs,java.ext.dirs默认为jdk\jre\lib\ext
    >>Bootstrap loader的查找url是sun.boot.class.path
    >>在程序运行后调用System.setProperty()来改变系统变量并不能改变以上加载的路径,因为classloader读取在System.setProperty之前.sun.boot.class.path是在程序中写死的,完全不能修改

    委派模型
    classloader有类需要载入时先让其parent搜寻其搜寻路径帮忙载入,如果parent找不到,在由自己搜寻自己的搜寻路径载入,ClassLoader hierachy本来就有这种性质

 


    NoClassDefFoundError和ClassNotFoundException
    NoClassDefFoundError:当java源文件已编译成.class文件,但是ClassLoader在运行期间在其搜寻路径load某个类时,没有找到.class文件则报这个错
    ClassNotFoundException:试图通过一个String变量来创建一个Class类时不成功则抛出这个异常
分享到:
评论

相关推荐

    自定义Java类加载器

    看完一个Java加载原理教程后,写了这个自己的类加载器,作个笔记,以便以且使用

    java 类加载器学习笔记1

    /** * java类加载器 * 类加载器负责加载与连接,这个过程是在运行时进行的,这种机制为java提供极大的灵活性 * * 类的生命周期 * class文件

    java基础学习笔记之类加载器

    java中的类加载器:java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap, ExtClassLoader,AppClassLoader类加载器也是一个java类,因此类加载器也需要被加载,所以...

    黑马程序员------类加载器学习注意点

    NULL 博文链接:https://840198532-qq-com.iteye.com/blog/1490901

    JVM学习笔记(一)——类的加载机制

    ​ 类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动...

    Maven笔记Maven笔记Maven笔记

    2.2: boot:含有plexus-classworlds类加载器框架 2.3: conf:含有settings.xml配置文件针对所有用户都有效果, ~/.m2/setting.xml对于单个用户的 3.3: lib: 含有Maven运行时所需要的java类库 Maven的约定(约定...

    连接器和加载器beta2.pdf

    这是一本介绍连接器和加载器的文章,比较底层,但对于提高操作系统级别编程的朋友特别有好处.

    Java学习笔记

    类加载器 ClassLoader 字节码校验器 解释执行器 (翻译)逐行的解释执行代码 2.安全 健壮 3.面向对象 面向过程: 程序:函数+变量 (算法+数据结构) 面向对象: 如:SmallTalk 程序:对象和对象相互之间的“通讯” ...

    java基础学习总结笔记

    Java基础常识、如何安装Java工具、Java语言的基础组成、Java面向对象、Java多线程、Java常用类、集合(重点)、IO流、GUI图形界面、网络编程、正则表达式、反射、注解、类加载器、动态代理等等,另外还包括银行管理...

    day020-继承加强和设计模式代码和笔记.rar

    由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用, 所以 不允许直接通过引用进行操作。 2. 拓展类加载器(Extension classLoader): 扩展类加载器是...

    Java基础全程学习笔记

    Java基础全程学习笔记,很早之前的学习笔记,补充发布。 从基础语法开始到类加载器、反射、模块化内容PDF形式发布

    【隐匿的学习笔记】JVM(1) 类加载子系统

    类的加载器和加载过程 其实就是JVM运行的第一步 讲class文件加载的过程 这个过程分为三步 加载 链接 初始化 也就是简图中的第一步 加载load 加载都做了什么? 1.通过类的全限定名获取定义此类的二进制字节流 ----&gt...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    15.2.1 类加载器层级架构 515 15.2.2 建立classloader实例 518 15.3 重点复习 520 15.4 课后练习 521 chapter16 自定义泛型、枚举与标注 523 16.1 自定义泛型 524 16.1.1 定义泛型方法 524 16.1.2 ...

    maven自学笔记.txt

    %Maven_HOME%\bin echo %M3_HOME% 连接符号:软链接实际上就是一个指向文件的指针 ...Boot 类加载器框架 conf 分为全局与用户配置 超级POM 在lib文件中 License.txt 软件的使用许可 NOTICE.txt 第三方的软件

    JVM性学习笔记-基本原理,内存模型,JVM参数

    JVM性学习笔记-基本原理,内存模型,JVM参数设置,类加载器原理,JDK自带工具

    RGB Blod三原色加载器CSS3特效.rar

    RGB Blod三原色加载器CSS3特效.rar

    wp-namespace-autoloader:一个PHP自动加载器类,该类遵循适用于PSR-4规范的WordPress编码标准

    一个PHP自动加载器类,该类遵循适用于PSR-4规范的WordPress编码标准 描述 命名空间和自动加载器很酷,可以帮助组织代码。 有了这些功能,您不必担心再次手动包含和要求php文件,并且您的代码可以组织在文件夹中。 ...

    J2SE笔记讲解个人修订(1.1).docx

    14 JAVA类加载器CLASSLOADER 15 JAVA简单工厂模式 16 JAVA中的注解 17 JAVA 图形界面 18 JAVA多线程 19 JAVA 反射机制 20 JAVA克隆CLONE(复制) 21 JAVA 网络编程 22 JAVA 其他未归类 23 JNI概述

Global site tag (gtag.js) - Google Analytics