多实例教程3 Java在需要使用类别的时候,才会将类别加载,Java的类别载入是由类别载入器(Class loader)来达到的,预设上,在程序启动之后,主要会有三个类别加载器:Bootstrap Loader、ExtClassLoader与AppClassLoader。 Bootstrap Loader是由C++撰写而成,预设上它负责搜寻JRE所在目录的classes或lib目录下的.jar档案中(例如rt.jar)是否有指定的类别并加载(实际上是由系统参数sun.boot.class.path指定);预设上ExtClassLoader负责搜寻JRE所在目录的lib/ext 目录下的classes或.jar中是否有指定的类别并加载(实际上是由系统参数java.ext.dirs指定);AppClassLoader则搜寻 Classpath中是否有指定的classes并加载(由系统参数java.class.path指定)。 Bootstrap Loader会在JVM启动之后载入,之后它会载入ExtClassLoader并将ExtClassLoader的parent设为Bootstrap Loader,然后BootstrapLoader再加载AppClassLoader,并将AppClassLoader的parent设定为 ExtClassLoader。 在加载类别时,每个类别加载器会先将加载类别的任务交由其parent,如果parent找不到,才由自己负责加载,如果自己也找不到,就会丢出 NoClassDefFoundError。 每一个类别被载入后,都会有一个Class的实例来代表它,每个Class的实例都会记得是哪个ClassLoader加载它的,可以由Class的getClassLoader()取得加载该类别的ClassLoader。 |
多方法实例教程2 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 { |
|( 京ICP备09078825号 )
GMT+8, 2024-11-24 01:18 , Processed in 0.136515 second(s), 42 queries .