zoukankan      html  css  js  c++  java
  • 深入类别载入器 forName() loadClass()

    转自:http://kevin_yang.itpub.net/post/172/31025

     1.java执行

    java.exe 是利用几个基本原则来寻找Java

    Runtime Environment(JRE),然后把类别档(.class)直接转交给JRE 执行之后,java.exe   就功成身退。类别加载器也是构成JRE 的其中一个重要成员,所以最后类别加载器就会自动从所在之JRE 目录底下的librt.jar 载入基础类别函式库。所以在上图里,一定是因为java.exe 定位到c:j2sdk1.4.0jre,所以才会有此输出结果。

    2.预先加载与依需求加载

    像基础类别函式库这样的加载方法我们叫做预先加载(pre-loading),这是因为基础类别函式库里头的类别大多是Java 程序执行时所必备的类别,所以为了不要老是做浪费时间的I/O 动作(读取档案系统,然后将类别文件加载内存之中),预先加载这些类别会让Java 应用程序在执行时速度稍微快一些。相对来说,我们自己所撰写的类别之加载方式,叫做依需求加载(load-on-demand),也就是Java 程序真正用到该类别的时候,才真的把类别文件从档案系统之中加载内存

     

    3.Java 提供两种方法来达成动态性

    一种是隐式的(implicit),另一种是显式的(explicit)。

    隐式的(implicit)方法我们已经谈过了,也就是当程序设计师用到new 这个Java 关键词时,会让类别加载器依需求加载您所需要的类别,这种方式使用了隐式的(implicit)方法。显式的方法,又分成两种方式,一种是藉由java.lang.Class 里的forName()方法,另一种则是藉由java.lang.ClassLoader 里的loadClass()方法。

    4.forName方法

    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, ClassLoader.getCallerClassLoader());而具有三个参数的forName()方法,最后叫用的是:forName0(name, initialize, loader);initialized的用法:true,表示载入实例的同时也载入静态初始化区块;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() ;

    }

    }

    则输出变成:

     

    5用显式的方法来达成动态性:直接使用类别加载器

    这种情形与使用Class 类别的forName()方法时,第二个参数传入false

    几乎是相同的结果。

    档案:Office.java

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

    Office off = new Office() ;

    System.out.println("类别准备载入") ;

    ClassLoader loader = off.getClass().getClassLoader() ;

    Class c = loader.loadClass(args[0]) ;

    System.out.println("类别准备实体化") ;

    Object o = c.newInstance() ;

    Object o2 = c.newInstance() ;

    }

    }

    执行

    java Office Word

    6自己建立类别加载器来加载类别

    档案: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() ;

    }

    }

    7类别被哪个类别载入器载入

    我们将上述的程序代码稍作修改,修改后的程序代码如下:

    档案: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 u1 = new URL("file:/d:/my/lib/") ;

    URLClassLoader ucl1 = new URLClassLoader(new URL[]{ u1 }) ;

    Class c1 = ucl1.loadClass(args[0]) ;

    Assembly asm1 = (Assembly) c1.newInstance() ;

    asm1.start() ;

    System.out.println(Office.class.getClassLoader()) ;

    System.out.println(u.getClass().getClassLoader()) ;

    System.out.println(ucl.getClass().getClassLoader()) ;

    System.out.println(c.getClassLoader()) ;

    System.out.println(asm.getClass().getClassLoader()) ;

    System.out.println(u1.getClass().getClassLoader()) ;

    System.out.println(ucl1.getClass().getClassLoader()) ;

    System.out.println(c1.getClassLoader()) ;

    System.out.println(asm1.getClass().getClassLoader()) ;

    }

    }

    执行后输出结果如下图:

    从输出中我们可以得知,Office.class 由AppClassLoader(又称做System Loader,系统加载器)所加载,URL.class 与URLClassLoader.class 由Bootstrap Loader 所加载(注意:输出null 并非代表不是由类别载入器所载入。在Java 之中,所有的类别都必须由类别加载器加载才行,只不过Bootstrap Loader 并非由Java 所撰写而成,而是由C++实作而成,因此以Java 的观点来看,逻辑上并没有Bootstrap Loader 的类别实体)。而Word.class 分别由两个不同的URLClassLoader 实体加载。至于Assembly.class,本身应该是由AppClassLoader 加载,但是由于多型(Polymorphism)的关系,所指向的类别实体(Word.class)由特定的加载器所加载,导致打印在屏幕上的内容是其所参考的类别实体之类别加载器。Interface 这种型态本身无法直接使用new 来产生实体,所以在执行getClassLoader()的时候,叫用的一定是所参考的类别实体的getClassLoader(),要知道Interface 本身由哪个类别加载器加载,您必须使用底下程序代码:Assembly.class.getClassLoader()

     

    8一切都是由Bootstrap Loader 开始 : 类别载入器的阶层体系

    当我们在命令列输入java xxx.class 的时候,java.exe 根据我们之前所提过的逻辑找到了

    JRE(Java Runtime Environment),接着找到位在JRE 之中的jvm.dll(真正的Java 虚拟机器),最后加载这个动态联结函式库,启动Java 虚拟机器。这个动作的详细介绍请回头参阅第一章。虚拟机器一启动,会先做一些初始化的动作,比方说抓取系统参数等。一旦初始化动作完成之后,就会产生第一个类别载入器,即所谓的Bootstrap Loader,Bootstrap Loader 是由C++所撰写而成(所以前面我们说,以Java 的观点来看,逻辑上并不存在Bootstrap Loader 的类别实体,所以在Java 程序代码里试图印出其内容的时候,我们会看到的输出为null),这个Bootstrap Loader 所做的初始工作中,除了也做一些基本的初始化动作之外,最重要的就是加载定义在sun.misc 命名空间底下的Launcher.java 之中的ExtClassLoader(因为是inner class,所以编译之后会变成

    Launcher$ExtClassLoader.class),并设定其Parent 为null,代表其父加载器为Bootstrap

    Loader。然后Bootstrap Loader 再要求加载定义于sun.misc 命名空间底下的Launcher.java 之中的AppClassLoader(因为是inner class,所以编译之后会变成Launcher$AppClassLoader.class),并设定其Parent 为之前产生的ExtClassLoader 实体。这里要请大家注意的是,Launcher$ExtClassLoader.class 与Launcher$AppClassLoader.class 都是由Bootstrap Loader 所加载,所以Parent 和由哪个类别加载器加载没有关系。我们可以用下图来表示:

     

    这个由Bootstrap Loader   ExtClassLoader   AppClassLoader,就是我们所谓「类别载入

    器的阶层体系」。

    在此要请大家注意的是,AppClassLoader 和ExtClassLoader 都是URLClassLoader 的子类别。由于它们都是URLClassLoader 的子类别,所以它们也应该有URL 作为搜寻类别档的参考,由原始码中我们可以得知,AppClassLoader 所参考的URL 是从系统参数java.class.path 取出的字符串所决定,而java.class.path 则是由我们在执行java.exe 时,利用 –cp 或-classpath 或CLASSPATH 环境变量所决定。我们可以用底下程序代码测试之:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

    String s = System.getProperty("java.class.path");

    System.out.println(s) ;

    }

    }

    输出结果如下:

    从这个输出结果,我们可以看出,在预设情况下,AppClassLoader 的搜寻路径为”.”(目前所在目录),如果使用-classpath 选项(与-cp 等效),就可以改变AppClassLoader 的搜寻路径,如果没有指定-classpath 选项,就会搜寻环境变量CLASSPATH。如果同时有CLASSPATH 的环境设定与-classpath 选项,则以-classpath 选项的内容为主,CLASSPATH 的环境设定与-classpath 选项两者的内容不会有加成的效果。

    至于ExtClassLoader 也有相同的情形,不过其搜寻路径是参考系统参数 java.ext.dirs。我们可以用底下程序代码测试:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

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

    System.out.println(s) ;

    }

    }

    输出结果如下:

    输出结果告诉我们,系统参数java.ext.dirs 的内容,会指向java.exe 所选择的JRE 所在位置下的

    libext 子目录。系统参数java.ext.dirs 的内容可以在一开始下命列的时候来更改,如下:

    最后一个类别加载器是Bootstrap Loader , 我们可以经由查询由系统参数

    sun.boot.class.path 得知Bootstrap Loader 用来搜寻类别的路径。请使用底下的程序代码测试之:

    档案:test.java

    public class test

    {

    public static void main(String args[])

    {

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

    System.out.println(s) ;

    }

    }

    输出结果如下:

    系统参数sun.boot.class.path 的内容可以在一开始下命列的时候来更改,如下:

     

    从这三个类别加载器的搜寻路径所参考的系统参数的名字中,其实还透漏了一个讯息。请回头看到java.class.path 与sun.boot.class.path,也就是说,AppClassLoader 与Bootstrap Loader会搜寻它们所指定的位置(或JAR 文件),如果找不到就找不到了,AppClassLoader 与Bootstrap Loader不会递归式地搜寻这些位置下的其它路径或其它没有被指定的JAR 檔。反观ExtClassLoader,所参考的系统参数是java.ext.dirs,意思是说,他会搜寻底下的所有JAR 文件以及classes 目录,作为其搜寻路径(所以您会发现上面我们在测试的时候,如果加入 -Dsun.boot.class.path=c:winnt选项时,程序的起始速度会慢了些,这是因为c:winnt 目录下的档案很多,必须花额外的时间来列举JAR 檔)。

    在命令列下参数时,使用 –classpath / -cp / 环境变量CLASSPATH 来更改AppClassLoader

    的搜寻路径,或者用 –Djava.ext.dirs 来改变ExtClassLoader 的搜寻目录,两者都是有意义的。可是用–Dsun.boot.class.path 来改变Bootstrap Loader 的搜寻路径是无效。这是因为AppClassLoader 与ExtClassLoader 都是各自参考这两个系统参数的内容而建立,当您在命令列下变更这两个系统参数之后, AppClassLoader 与ExtClassLoader 在建立实体的时候会参考这两个系统参数,因而改变了它们搜寻类别文件的路径;而系统参数sun.boot.class.path 则是预设与Bootstrap Loader 的搜寻路径相同,就算您更改该系统参与,与Bootstrap Loader 完全无关。如果您手边有原始码, 以JDK 1.4.x 为例, 请参考< 原始码根目录>hotspotsrcsharevmruntimeos.cpp 这个档案里头的os::set_boot_path 方法,您将看到程序代码片段:

    static const char classpathFormat[] =

    "%/lib/rt.jar:"

    "%/lib/i18n.jar:"

    "%/lib/sunrsasign.jar:"

    "%/lib/jsse.jar:"

    "%/lib/jce.jar:"

    "%/lib/charsets.jar:"

    "%/classes";

    JDK 1.4.x 比JDK 1.3.x 新增了一些核心类别函式库(例如jsse.jar 与jce.jar),所以如果您测试时用的是1.4.x 版的JDK,sun.boot.class.path 内容应该如下:

     

    9. 委派模型

    如果您对整个类别加载的方式仍有所疑问,请容笔者重新解释一下之前程序

    档案:Office.java

    import java.net.* ;

    public class Office

    {

    public static void main(String args[]) throws Exception

    {

  • 相关阅读:
    ldconfig和ldd用法
    Linux上ld和ld.so命令的区别
    一维二维码的提取、识别和产生
    最大轮廓和投影
    如何做出半透明和闪光效果
    马赫效应和应对方法
    钢管识别项目1
    钢管识别项目2
    选择轮廓(select_shape)
    压板识别项目分析
  • 原文地址:https://www.cnblogs.com/kelin1314/p/1867542.html
Copyright © 2011-2022 走看看