论坛首页 入门技术论坛

深入学习Java(三)

浏览 1562 次
该帖已经被评为新手帖
作者 正文
   发表时间:2008-07-15  
OO
学习笔记
三、深入类载入器(classLoader)
    Java是一种天生就具有动态连结能力的技术。Java把每个类、接口编译之后,全部变成一个个小的执行单位(.class文件)。一旦指定一个具有public static void main(String[] arg)方法的类作为起点开始运行后,JVM会找出所有在执行时需要的执行单位,并将它们载入内存之中,彼此交互运行。尽管本质上是一堆类文件,但是在内存中变成了一个“逻辑上”为一体的Java应用程序。所以,严格来说,每个类文件对JVM都是一个独立的动态连结函数库,不过他的类型不是.dll或.so而是.class罢了。因为有这种特性,可以在不重新编译其他Java程序的情况下,只修改有问题的执行单位,并放入系统中等到下次该JVM重启时,这个逻辑上的java应用程序就会因为载入新修改的.class文件,自己的功能也做了更新。这是一个基本的动态性功能。

    可是,这需要重启JVM,若要不重启JVM且读取新版本后释放旧版本内存就要用到类载入器(classLoader)。

    Java.exe是利用几个基本原则寻找JRE后把类可执行文件(.class文件)直接转交给JRE执行后,java.exe就功成身退。类载入器(classLoader)会自动从所在的JRE目录下的\lib\rt.jar载入基类函数库。载入分为两种:预先载入(pre-loading)和依需求载入(load-on-demand)。一般基类函数库都是都是预先载入,而自己撰写的函数为依需求载入(只有声明没有实例,在依需求载入的时候是不会载入的)。依需求载入的优点是节省内存,缺点是当程序第一次用到该类的时候,系统必须花一些额外的时间来载入该类,使得整体的执行效率受到影响。

    让Java程序具有动态性的两种方法:
    1、一种是隐式的(implicit),另一类是显式的(explicit)。两种方法底层机制完全相同,只是所使用的代码有所不同。隐式的(implicit)方法:当程序员用到new关键字时,类载入器依需求载入所需要的类。但仍有限制,无法达成更多的弹性。显式的(explicit)又分为两种方式,一种由java.lang.Class里的forName()方法,另一种则由java.lang.ClassLoader里的loadClass()方法。
    2、使用Class.forName()方法的显式方法达成动态性:
    Office.java
   
public class Office {
    public static void main(String args[]) throws Exception {
        Class c = Class.forName(args[0]) ;
        Object o = c.newInstance() ;
        Assembly a = (Assembly) o ;
        a.start() ;
    }
}

    Word.java
  
 public class Word implements Assembly {
    public void start() {
        System.out.println("Word starts") ;
    }
}


    主程序Office.java编译后,以后只要调用:java Office Word 就可以动态载入需要的类。(当类载入器载入的类继承了其他的类,或是实现了其他的接口,就会先载入该实现的接口类,也会载入其父类,如果父类也有其父类,也会一并优先载入。换句话说,类载入器会依照继承体系最上层的类往下依次载入,直到所有的祖先类都载入了,才轮到自己载入。)

    在JDK的Class类中其实有两个forName()方法,一个是一个参数的(就是之前程序中使用的):
public static Class forName(String className)

另外一个是需要三个参数的:
public static Class forName(String name, boolean initialize,ClassLoader loader)
这两个方法,最后都是连接到原生方法forName0(),其声明如下:
private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException;
只有一个参数的forName()方法,最后调用的是:
forName0(className, true,lassLoader.getCallerClassLoader());
而具有三个参数的forName()方法,最后调用的是:
forName0(name, initialize, loader);
其中initialize参数使用true 和false 会造成不同效果。为false时,测试如下:
Office.java
public class Office{
    public static void main(String args[]) throws Exception{
        Office off = new Office();
        System.out.println("类准备输入");
        Class c = Class.forName(args[0], false,off.getClass().getClassLoader());
        System.out.println("类准备实例化");
        Object o = c.newInstance();
        Object o2 = c.newInstance();
    }
}

Word.java
public class Word implements Assembly{
    static{
        System.out.println("Word static initialization");
    }
    public void start(){
    System.out.println("Word starts");
    }
}

运行:java Office Word 时的结果如下:

即使类被载入了,其静态初始化模块(static initialization block)也不会被调用,而是在第一次调用newInstance()方法时才被调用。
「静态初始化模块实在类第一次被实例化的时候才会被调用那仅仅一次」
   
    得到以下结论:不管使用new来产生某类的实例或是使用只有一个参数的forName()方法,内部都隐含了“载入类+调用静态初始化模块”的动作。而是要具有三个参数的forName()方法时,如果第二个参数给定的是false,那么就会命令类载入器载入该类,但不会调用其静态初始化模块,只有等到整个程序第一次实例化某个类时,静态初始化模块才会被调用。
   
    3、直接使用类载入器的显式方法达成动态性:
    类是一个样板,而物件就是根据这个模板产生的实例。在java中,每个类最后的祖先都是Object,而Object里有一个getClass()的方法,用来获取某个特定实例所属类的参数,这个参数指向的是一个名为Class类(Class.class)的实例,这个实例在类文件(.class)第一次载入内存时创建的,以后程序中产生任何该类的实例,这些实例的内部都会有一个栏位记录着这个Class类的所在位置。每个Class类的实例,都可以当作某个类在内存中的代理人。

    系统中可以同时存在多个ClassLoader的实例,而且一个类载入器(ClassLoader的实例)可以载入多个类。Java中的类都是由某个类载入器(ClassLoader的实例)来载入。所以只要取得Class实例的参数,就可以利用其getClassLoader()方法来取得载入该类的类载入器的参数。最后,取得了ClassLoder的实例,调用loadClass()方法载入需要的类。
Office1.java
public class Office1{
    public static void main(String args[]) throws Exception{
        Office1 off = new Office1();
        System.out.println("类准备输入");
        Class c = Class.forName(args[0], false,off.getClass().getClassLoader());
        System.out.println("类准备实例化");
        Object o = c.newInstance();
        Object o2 = c.newInstance();
    }
}

Word1.java
  
 public class1 Word implements Assembly {
    public void start() {
        System.out.println("Word starts") ;
    }
}

运行:java Office1 Word1 时的结果如下:

   
    4、自己创建类载入器来载入类:
利用Java本身提供的java.net.URLClassLoader类就可以做到:
Office.java
import java.net.* ;
public class Office{
    public static void main(String args[]) throws Exception{
        URL u = new URL("file:/d:/my/lib/");
        URLClassLoader ucl = new URLClassLoader(new URL[]{ u });
        Class c = ucl.loadClass(args[0]);
        Assembly asm = (Assembly) c.newInstance();
        asm.start();
    }
}

URL可以指定网络上的任何位置,也可以指定本身计算机上的文件系统(包含jar文件)。

    5、类载入器的阶层体系:
流程图如下:
[img]http://skyKing.iteye.com/upload/picture/pic/19023/dbf8e2fe-b683-3b6e-842f-a9d0e186ea9c.jpg [/img]
其中Bootstrap Loader->ExtClassLoader->AppClassLoader,就是类载入器的阶层体系。
test.java
public class test{
    public static void main(String args[]){
        ClassLoader cl = test.class.getClassLoader() ;
        System.out.println(cl) ;
        ClassLoader cl1 = cl.getParent() ;
        System.out.println(cl1) ;
        ClassLoader cl2 = cl1.getParent() ;
        System.out.println(cl2) ;
    }
}

输出结果如下:
[img] http://skyKing.iteye.com/upload/picture/pic/19019/c7d93621-de11-3c48-9672-7b77ad44a8dc.jpg [/img]
AppClassLoader 和ExtClassLoader 都是URLClassLoader的子类。
AppClassLoader 的URL参数 是由从系统参数java.class.path 取出的字串所决定,而java.class.path 则是执行java.exe 时,利用 –cp 或-classpath 或CLASSPATH 环境变量所决定。

在预设情況下,AppClassLoader 的搜寻路径为”.”(当前所在目录),如果使用-classpath 选项(与-cp 等效),就可以改变AppClassLoader 的搜寻路径,如果沒有指定-classpath 选项,就搜寻环境变量CLASSPATH。如果同时有CLASSPATH 的环境设定与-classpath 选项,则以-classpath 选项的内容为主,CLASSPATH 的环境设定与-classpath 选项两者的內容不会有加成的效果。

ExtClassLoader 也有相同的情形,不过其搜寻路径是参考系统参数java.ext.dirs。
系統参数java.ext.dirs 的內容,会指向java.exe 所选择的JRE 所在位置下的\lib\ext 子目录。

Bootstrap Loader,搜寻路径是参考系统参数sun.boot.class.path ,测试代码如下:
Test3.java
public class Test3  {
    public static void main(String[] args) {
        String s = System.getProperty("sun.boot.class.path");
        System.out.println(s);
    }
}

输出结果如下:
[img]http://skyKing.iteye.com/upload/picture/pic/19017/c4a6432a-c6b0-32c1-aee9-1af72d61a2b5.jpg [/img]

总述:路径java.class.path 与sun.boot.class.path,也就是AppClassLoader 与Bootstrap Loader 会搜寻的位置(或JAR 文件),如果找不到就找不到了,AppClassLoader 与Bootstrap Loader 不会递归搜寻这些位置下的 路径或其他没有被指定的JRE文件。ExtClassLoader参考的系统参数是java.ext.dirs,他会搜寻底下的所有JAR 文件以及classes 目录,作為其搜寻路径。

AppClassLoader 与ExtClassLoader 是各自参考系统参数sun.boot.class.path与java.ext.dirs容而建立,在命令行下更改这两个系统参数, AppClassLoader 与ExtClassLoader 在建立实例的时候会参考这两个系统参数,因而会改变他们搜寻类文件的路径;而系統参数sun.boot.class.path 则是预设与Bootstrap Loader 的搜寻路径相同,就算更改该系统参数,也与Bootstrap Loader 完全无关。
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics