java深度历险

如果安装 JDK1.3那么安装程序一定会同时安装两套 JRE。 一套位于 jdk\jre 目录 一套位于 program files\JavaSoft 目录 如果是 JDK 1.4可以选择是否安装 program files\java 目录下的 jre,但是 jdk 安装目录下的 jre 这套 jre 必须安装 JRE 与 PC 比较 JRE: java 类函数库>原生函数库.dll>JAVA 虚拟机(jvm.dll)>帮助函数库.dll PC: Win32 API .dll>CPU 编写好的 Java 源文件必须要有 JRE 才能帮助我们运行,Java 虚拟机只是 JRE 里的一个 成员而已,或者说 jvm 只是 jre 里头一个动态连接函数库, jdk 里面的 jre 一般用于运行 java 本身的程序,比如 javac,等等.programfiles 下面的 jre 用 于运行用户编写的 java 程序. JRE 下的 bin\client 或者 bin\server 的 jvm.dll 就是 JVM 了 ---------------------------在刚装好 jdk,没有对计算机进行任何设置时,进入命令行窗口 C:\Documents\Administrator>java -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode, sharing) C:\Documents\Administrator>java -server -version Error: no `server' JVM at `C:\Program Files\Java\jre1.5.0_11\bin\server\jvm.dll' ----------------------------------当设置 path 路径中包含 jdk\bin 目录后 ---------------------------C:\>set path="C:\Program Files\Java\jdk1.5.0_11\bin"; %path%; C:\>java -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode, sharing) C:\>java -server -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Server VM (build 1.5.0_11-b03, mixed mode) -server 的-version 就可以显示出来了 ---------------------------JDK 里用 Java 所写的开发工具 如 javac.exe jar.exe 都存放在 JDK 安装目录\lib\tools.jar 这个档案中 javac.exe 只是一个包装器(wrapper),而制作目的是为了让开发者免于输入太长的指令。 实际等于: java -classpath x:\jdk1.xx\lib\tools.jar com.sun.tools.javac.Main 当用 j2me 开发 palm 应用程序的时候, 工具会帮我们打包 jar 档,然后用一个 RPC 档的 外壳罩住,让 java 程序看起来像是一个原生(native)的应用程序。 用.NET 开发出来的执行文件也是一个包装器的概念。 JDK 里面的工具几乎全是用 java 所写的,所以 JDK 本身就是 Java 应用程序,因此要用 JDK 附的工具来开发 Java 程序, 也必须要自行附一套 JRE 才行。这就是 JDK 安装目录\jre 下需要一套 JRE 的原因。 位于 program files\下的那套 JRE 就是拿来执行我们自己写的 java 应用程序。不过,两 套中任何一套 JRE 都可以拿来执行我们所撰写的 Java 应用程序, 可是 JDK 内附的开发工具在预设使用包装器(.exe) 来启动的情形下,都会自己去选用 \jre 底下那套

JRE。 -----------------------到底是执行哪一个 java.exe java xxx 当一台机器上有多个 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 后有一个验证程序,verify.dll 验证 jre 和 java.exe 的版本是否一致, 如果不一致则会发生错误 一般把常用的工具档放到 JDK 目录\jre\lib\ext 下 把有关安全机制的配置文件放到 JDK 目录\jre\lib\security 下 调用了其他 Java 函数库的程序,在编译阶段没有问题,可是却无法执行,显示 ClassNotFoundException 的原因可能是: 在 system32和 jdk\bin 目录下都有 java.exe 而 javac.exe 只有在 jdk\bin 目录下有 javac.exe 会自动调用 JDK 所在目录下的那套 JRE , 因此在编译时 JVM 会找到函数库, 所以编译不会发生问题, 但在执行时,键入 java xxx 的时候会优先执行 system32 下的 java.exe 因此会自动调用 program files 目录下的那套 JRE(稍后解释) 所以要执行就必须把外部 jar 文件放到相应 jre\lib\ext 目录下 JDK\jre\bin\下有两个目录 server,client 两个目录下都会有 jvm.dll client 目录下的 jvm.dll 较小 server 目录下的较大 ----------------------系统默认 path C:\Documents\Administrator>set path Path=C:\WINDOWS\system32; C:\WINDOWS; C:\WINDOWS\System32\Wbem PATHEXT=.COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS; .JSE; .WSF; .WSH 在 system32目录 底下找不到 JRE 目录, c:\windows 目录 也找不到 JRE 目录的情况 在 下,根据下一个逻辑,就是去查询注册表 C:\Program Files\Java\jre1.xx 该目录下的 bin 子目录却只有看到 client 子目录,却没有 看到 server 子目录。这就是为何在一开始执行 java -server -version 会报错的原因 ------------------------------------ ============================ ============================ JAVA 类加载器 ============================ ============================ 有了动态性, 我们的应用程序就可以在不用全盘重新编译的情况下更新系统, 或者在不 用停止主程序运作的情况下,除去系统中原有的 bug,或者是增加原本不具备的新功能。 一般来说,常见的程序语言先天上并不具有动态性的本质,如 C、C++本身就不具备动 态性。因此,为了让这些本身不具有动态性的程序语言具有某种程度的动态性, 就必须依赖底层的操作系统提供一些机制来实现动态性,Windows 操作系统底下的动 态联结函式库(Dynamic Linking Library) 和 Unix 底下的共享对象(Share Object) 要运用这些底层操作系统所提供的机制必须多费一些功夫来撰写额外的程序代码(例如 Windows 平台上需要使用 LoadLibrary() 与 GetProcAddress()

, 两个 Win32 API 来完成动态性的需求),这些额外撰写的程序代码也会因为作业平台的 不同而不同,毕竟这些额外的程序代码与程序本身的运作逻辑甚少关联, 所以维护起来虽不算复杂,但仍有其难度。 每个类对 java 机来说, 都是一个独立的动态联结函数库, 只不过扩展名不是.dll 或.so 而 是.class 所以可以在不重新编译其他 java 程序代码的情况下,只修改需要修改的执行单位,并 放入文件系统中,等下次该 java 虚拟机重新启动时, 这个逻辑上的 Java 应用程序就会因为加载了新修改的.class 文件,自己的功能也做了更 新。这是一个最基本的动态性功能。 JSP/Servlet 之类的 Web Container 或者高档的 Application Server 里的 EJB Container 他 们会提供一个 Hot Deployment 功能。 即在不关闭 Web Server 的情况下,放入已编译好的新 Servlet 以取代旧的 Servlet,下次 Http request 时,就会自动释放旧的 servlet 而重载新的 servlet。 程序运行时需要的核心类库位于 jre\lib\rt.jar 中 类加载器的作用就是把类从表态的硬盘 .class 文件,复制一份到内存中,并做一此 始 化工作 java.exe 就是利用几个原则找到 JRE 后,把.class 直接转交给 JRE 运行后便功成身退 public class test { public static void main(String[] args) { System.out.println("Hello DD"); } } javac test.java java -verbose:class test -----------------------------------------------------classloader 的两种载入方式: 1)pre-loading 预先载入,载入基础类 2)load-on-demand 按需求载入 只有实例化一个类时,该类才会被 classloader 载入,仅仅申明并不会载入 基础类库是预先加载的(pre-loading) 用户所写的一般类是按需加载的(load-on-demand) 按需加载 三个类 public class A { public void print() { System.out.println("Using Class A"); } } public class B { public void print() { System.out.println("Using Class B"); } } public class Main { public static void main(String[] args) { A a=new A(); B b; a.print(); } } javac *.java java -verbose:class Main [Loaded Main from file:/C:/] [Loaded A from file:/C:/] Using Class A [Loaded https://www.360docs.net/doc/0715580564.html,ng.Shutdown from shared objects file] [Loaded https://www.360docs.net/doc/0715580564.html,ng.Shutdown$Lock from shared objects file] 没有看到 [Loaded Main from file:/C:/] [Loaded A from file:/C:/] [Loaded B from file:/C:/] -------------------------------------------动态加载的例子 三个类 public class Word { public void start() { System.out.println("Word start"); } } public class Excel { public void start() { System.out.println("Excel start"); } } public class Office { public static void main(String[] args) { if(args.length!=1) { return ; } if(args[0].equals("Word")) { Word w=new Word(); w.start(); }else if(args[0].equals("Excel")) { Excel e=new Excel(); e.start(); } } } 依需求加载的优点是节省内存,但是仍有其缺点。举例来说,当程序第一次

用到该类别 的时候,系统就必须花一些额外的时间来加载该类别,使得整体执行效能受到影响, 尤其是由数以万计的类别所构成的 Java 程序。可是往后需要用到该类别时,由于类别 在初次加载之后就会被永远存放在内存之中,直到 Java 虚拟机关闭, 所以不再需要花费额外的时间来加载。 总的来说,就弹性上和速度上的考虑,如此的设计所带来的优点(弹性和省内存)远超过 额外加载时间的花费(只有第一次用到时),因此依需求加载的设计是明智的选择。 如果我们新增了 Access.java 和 PowerPoint.java 这两个新类别时, Office.java 里的主程序就必须增加两个 if … else 的循环 那么如何来更好的展示 java 在可扩展性的优势呢 ---------------------------------------------------------使 JAVA 程序更有动态性的方法有两种 1)implicit 隐式,即利用实例化才载入的特性来动态载入 class 2)explicit 显式方式,又分两种方式: 1)https://www.360docs.net/doc/0715580564.html,ng.Class 的 forName()方法 2)https://www.360docs.net/doc/0715580564.html,ng.ClassLoader 的 loadClass()方法 隐式的:new 关键字 生成类的实例 第一种方法: Class.forName() 加载类 一个接口 public interface Assembly { public void start() ; } 三个类 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 ; */ Assembly a = (Assembly) c.newInstance(); a.start() ; } } public class Word implements Assembly { public void start() { System.out.println("Word Start") } } public class Excel implements Assembly { public void start() { System.out.println("Excel Start") } } -------------------------------------------有两个 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,ClassLoader.getCallerClassLoader()); 而具有三个参数的 forName()方法,最后调用的是: forName0(name, initialize, loader); 关于名为 loader 这个参数的用法 public class Office { public static void main(String args[]) throws Exception { Class c = Class.forName(args[0],true,null) ; //line /*Object o = c.newInstance() ; Assembly a = (Assembly) o ; */ Assembly a = (Assembly) c.newInstance(); a.start() ; } } C:\>java Office Excel Exception in thread "main" https://www.360docs.net/doc/0715580564.html,ng.ClassNotFoundException: Excel at https://www.360docs.net/doc/0715580564.html,ng.Class.forName0(Native Method) at https://www.360docs.net/doc/0715580564.html,ng.Class.forName(Class.java:242) at Office.main(Office.java:9) //line 前加上一行,再修改改为 Office

o=new Office(); Class c=Class.forName(args[0],true,o.getClass().getClassLoader()); 最终代码为 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); Class c = Class.forName(args[0],true,o.getClass().getClassLoader()) ; Assembly a = (Assembly) c.newInstance(); a.start() ; } } 就可以运行了 只有一个参数的 forName() 方法,由于在内部使用了 ClassLoader.getCallerClassLoader() 来取得加载呼叫他的类别所使用的类别加载器, 和我们自己写的程序有相同的效用。( 注意,ClassLoader.getCallerClassLoader()是一个 private 的方法,所以我们无法自行叫用, 因此必须要自己产生一个 Office 类别的实例,再去取得加载 Office 类别时所使用的类 别加载器)。 三个参数的 Class.forName()的第二参数 给类添加一个静态代码块 public class Word implements Assembly { static { System.out.println("Word Static Initialization"); } public void start() { System.out.println("Word start"); } } public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); System.out.println("类准备载入"); //Class c=Class.forName(args[0],true,o.getClass().getClassLoader()); Class c=Class.forName(args[0],false,o.getClass().getClassLoader()); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); } } 为 true 时 C:\>java Office Word 类准备载入 Word Static Initialization 准备实例化 Word start Word start 为 false 时 C:\>java Office Word 类准备载入 准备实例化 Word Static Initialization Word start Word start 静态初始化区块是在类别第一次被实例化的时候才会被呼叫那仅仅一次 注意: 不管您使用的是 new 来产生某类别的实例、或是使用只有一个参数的 forName()方法, 内部都隐含了”加载类别+呼叫静态初始化区块”的动作。 而使用具有三个参数的 orName()方法时,如果第二个参数给定的是 false,那么就只会 命令类别加载器加载该类别,但不会叫用其静态初始化区块, 只有等到整个程序第一次实例化某个类别时,静态初始化区块才会被叫用 static 块在什么时候执行? 1)当调用 forName(String)载入 class 时执行,如果调用 ClassLoader.loadClass 并不会执 行.forName(String,false,ClassLoader)时也不会执行. 2) 如 果 载 入 Class 时 没 有 执 行 static 块 则 在 第 一 次 实 例 化 时 执 行 . 比 如 new ,Class.newInstance()操作 3)static 块仅执行一次 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); System.out.println("类准备载入"); ClassLoader loader=o.getClass().getClassLoader(); Class c=loader.loadClass(args[0]); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); }

} -----------------------------------------------第二种:直接使用 ClassLoader 类别的 loadClass() 方法来加载类 此方式只会把类加载至内存, 并不会调用该类别的静态初始化区块, 而必须等到第一次 实例化该类别时,该类别的静态初始化区块才会被叫用。 这种情形与使用 Class 类别的 forName()方法时, 第二个参数传入 false 几乎是相同的结 果。 另一种方式 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); Class co=Office.class; System.out.println("类准备载入"); ClassLoader loader=co.getClassLoader(); Class c=loader.loadClass(args[0]); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); } } 归纳 1. Office o=new Office(); ClassLoader loader=o.getClass().getClassLoader(); 调用对象的 getClass()方式取得该对象的引用,再调用该引用的 getClassLoader()方法取得 该对象类加载器的引用 2. Office o=new Office(); Class co=Office.class; ClassLoader loader=co.getClassLoader(); 直接定义一个此对象类的.class 类引用然后由此对象的 getClassLoader()方法取得该对 象类加载器的引用 然后 Class c=loader.loadClass(args[0]); Class 类的实例. >>Class 类无法手工实例化,当载入任意类的时候自动创建一个该类对应的 Class 的实例, >>某个类的所有实例内部都有一个栏位记录着该类对应的 Class 的实例的位置., >>每个 java 类对应的 Class 实例可以当作是类在内存中的代理人.所以当要获得类的信 息(如有哪些类变量,有哪些方法)时,都可以让类对应的 Class 的实例代劳。 java 的 Reflection 机制就大量的使用这种方法来实现 >>每个 java 类都是由某个 classLoader(ClassLoader 的实例)来载入的,因此 Class 类别的 实例中都会有栏位记录他的 ClassLoader 的实例,如果该栏位为 null, 则表示该类别是由 bootstrap loader 载入的(也称 root laoder),bootstrap loader 不是 java 所 写成,所以没有实例. ------------------------------------------------------自己建立类别加载器来加载类别 利用 Java 本身提供的 https://www.360docs.net/doc/0715580564.html,.URLClassLoader 如 实 例 化 一 个 URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader 优先找当前目录,再在 url 中找.class 加 载.URL 中别忘在最后加"/"表示目录 import https://www.360docs.net/doc/0715580564.html,.*; public class Office { public static void main(String[] args) throws Exception { if(args.length!=1) { return ; }else { URL u=new URL("http://share/"); URLClassLoader ucl=new URLClassLoader(new URL[]{u}); Class c=ucl.loadClass(args[0]); Assembly asm=(Assembly)c.newInstance(); asm.start(); } } } -----------------------------------------------import https://www.360docs.net/doc/0715580564.html,.*; public class Office { public static void main(String[] args) throws Exception { URL u = new URL("Http

://share/") ; URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ; Class c = ucl.loadClass(args[0]) ; Assembly asm = (Assembly) c.newInstance() ; asm.start() ; URL u1 = new URL("Http://share/") ; 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()) ; } } C:\>java Office Word Word Static Initialization Word start Word start https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 null null https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 null null https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 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() --------------------------------------------------------------------------------------------一切都是由 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 和由哪个类别加载器加载没有关系 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) ; } } C:\>java test https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@923e30 null AppClassLoader 和 ExtClassLoader 都是 URLClassLoader 的子类别。由于它们都是 URLClassLoader 的子类别,所以它们也应该有 URL 作为搜寻类别档的参考, 由原始码中我们可以得知, 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 Bootstrap Loader ,我们可以经由查询由系统参数 sun.boot.class.path 得知 Bootstrap Loader 用来搜寻类别的路径 java -Dsun.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:\windows 选项时, 程序的起始速度会慢了些,这是因为 c:\windows 目录下的文件很多,必须花额外的时 间来列举 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 的搜寻路径相同, 就算您更改该系统参与,与 BootstrapLoader 完全无关 AppClassLoader 与 ExtClassLoader 在整个虚拟机之中只会存有一份,一旦建立了,其 内部所参考的搜寻路径将不再改变, 也就是说,即使我们在程序里利用 System.setProperty() 来改变系统参数的内容,仍然 无法更动 AppClassLoader 与 ExtClassLoader 的搜寻路径。 因此, 执行时期动态更改搜寻路径的设定是不可能的事情。 ------------------------------------------委拖模型 Bootstrap Loader 所做的初始工作中,除了也做一些基本的初始化动作之外,,最重要 的就是加载定义在 sun.misc 命名空间底下的 Launcher.java 之中的 ExtClassLoader, 并设定其 Parent 为 null, 然后 Bootstrap Loader 再加载定义在 sun.misc 命名空间底下的 Launcher.java 之中的 AppClassLoader, 并设定其 Parent 为之前产生的 ExtClassLoader 实例。 类加载器有加载类别的需求时,会先请示其 Parent 使用其搜寻路径帮忙加载,如果 Parent 找不到,那么才由自己依照自己的搜寻路径搜寻类别 两个类 public class test{ public static void main(String args[]) { System.out.println(test.class.getClassLoader()) ; testlib tl = new testlib() ; tl.print() ; } } public class testlib{ public void print() { System.out.println(this.getClass().getClassLoader()) ; } } 将这两个源文件编译成.class 文件以后 复制两份 分别至于\classes 底下 ( 注意, 您的系统下应该没有此目录, 您必须自己建立) 与<> 录>\lib\ext\classes(注意,您的系统下应该没有此目录,您必须自己建立)底下, C:\Program Files\Java\jdk1.5.0_11\jre\classes C:\Program Files\Java\jdk1.5.0_11\jre\lib\ext\classes c:\myclasses 测试一: \classes 底下 test.class testlib.class \lib\ext\classes 底下 test.class testlib.class c:\

myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test null null 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。 由于\classes 目录为 Bootstrap Loader 的搜寻路径之一, 所以 Bootstrap Loader 找到了 test.class ,因此将它加载。 接着在 test.class 之内有加载 testlib.class 的需求, 由于 test.class 是由 Bootstrap Loader 所 加载,所以 testlib.class 内定是由 Bootstrap Loader 根据其搜寻路径来寻找, 因为 testlib.class 也位于 Bootstrap Loader 可以找到的路径下, 所以也被加载了。 最后我们看到 test.class 与 testlib.class 都是由 Bootstrap Loader(null) 载入。 测试二: \classes 底下 test.class \lib\ext\classes 底下 test.class testlib.class c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test null Exception in thread "main" https://www.360docs.net/doc/0715580564.html,ng.NoClassDefFoundError: testlib at test.main(test.java:4) 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。 由于\classes 目录为 Bootstrap Loader 的搜寻路径之一, 所以 Bootstrap Loader 找到了 test.class ,因此将它加载。 接着在 test.class 之内有加载 testlib.class 的需求, 由于 test.class 是由 Bootstrap Loader 所 加载,所以 testlib.class 内定是由 Bootstrap Loader 根据其搜寻路径来寻找,但是因为 Bootstrap Loader 根本找不到 testlib.class( 被我们删 除了),而 Bootstrap Loader 又没有 Parent,所以无法加载 testlib.clss 。 输 出 告 诉 我 们 , test.class 由 Bootstrap Loader 加 载 , 且 最 后 印 出 的 讯 息 是 NoClassDefFoundError, 代表无法加载 testlib.class 。 这个问题没有比较简单的方法能够解决, 但是仍然可以透过较复杂的 Context Class Loader 来解决 测试三: \classes 底下 testlib.class \lib\ext\classes 底下 test.class testlib.class c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@7259da null 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。但是 Bootstrap Loader 无法在其搜寻路径下找到 test.class( 被我们删 掉了),所以 ExtClassLoader 只得自己搜寻。因此 ExtClassLoader 在其搜寻路径 \lib\ext\classes 底下找到 test.class ,因此将它加载。接着在 test.class 之内有加载 testlib.class 的需求,由于 test.class 是由 ExtClassLoader 所加载, 所 以 testlib.class 内 定 是 由 ExtClassLoader 根 据 其 搜 寻 路 径 来 寻 找 , 但

是 因 为 ExtClassLoader 有 Parent,所以要先由 Bootstrap Loader 先帮忙寻找,testlib.class 位于 Bootstrap Loader 可以找到的路径下,所以被 Bootstrap Loader 加载了。最后我们 看到 test.class 由 ExtClassLoader 载入,而 testlib.class 则是由 Bootstrap Loader(null)载入。 测试四: \classes 底下 \lib\ext\classes 底下 test.class testlib.class c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@7259da https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@7259da 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。但是 Bootstrap Loader 无法在其搜寻路径下找到 test.class( 被我们删 掉了),所以 ExtClassLoader 只得自己搜寻。因此 ExtClassLoader 在其搜寻路径 \lib\ext\classes 底下找到 test.class ,因此将它加载。接着在 test.class 之内有加载 testlib.class 的需求,由于 test.class 是由 ExtClassLoader 所加载, 所以 testlib.class 内定是由 ExtClassLoader 根据其搜寻路径来寻找,ExtClassLoader 一 样要请求其 Parent 先试着加载,但是 Bootstrap Loader 根本找不到 testlib.class ( 被我们删除了),所以只能由 ExtClassLoader 自己来,ExtClassLoader 在其搜寻路径 \lib\ext\classes 底下找到 testlib.class ,因此将它加载。 最后我们看到 test.class 与 testlib.clas 都是由 ExtClassLoader 载入。 测试五: \classes 底下 \lib\ext\classes 底下 test.class c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@7259da Exception in thread "main" https://www.360docs.net/doc/0715580564.html,ng.NoClassDefFoundError: testlib at test.main(test.java:4) 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。但是 Bootstrap Loader 无法在其搜寻路径下找到 test.class( 被我们删 掉了),所以 ExtClassLoader 只得自己搜寻。因此 ExtClassLoader 在其搜寻路径 \lib\ext\classes 底下找到 test.class ,因此将它加载。接着在 test.class 之内有加载 testlib.class 的需求,由于 test.class 是由 ExtClassLoader 所加载, 所以 testlib.class 内定是由 ExtClassLoader 根据其搜寻路径来寻找,ExtClassLoader 一 样要请求其 Parent 先试着加载,但是 Bootstrap Loader 根本找不到 testlib.class ( 被我们删除了),所以只能由 ExtClassLoader 自己来,ExtClassLoader 也无法在自己 的搜寻路径中找到 testlib.class ,所以产生错误讯息。输出告诉我们,test.class 由 ExtClassLoader 加载,且最后印出的讯息是 NoClassDefFoundError,代表无法加载 testlib.class 。要解决问题,我们必须让 ExtClassLoader 找的到 testli

b.class 才行, 所以请使用选项 –Djava.ext.dirs=<路径名称> 来指定,请注意,ExtClassLoader 只会自 动搜寻底下的 classes 子目录或是 JAR 文件, 其他的子目录或其他类型的档案一概不管。此外,这个错误亦可以透过 Context Class Loader 的技巧来解决。 测试六: \classes 底下 \lib\ext\classes 底下 testlib.class c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@197d257 https://www.360docs.net/doc/0715580564.html,uncher$ExtClassLoader@7259da 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。 Bootstrap Loader 无法在其搜寻路径下找到 test.class( 被我们删掉了) , 所以转由 ExtClassLoader 来搜寻。ExtClassLoader 仍无法在其搜寻路径 \lib\ext\classes 底下找到 test.class,最后只好由 AppClassLoader 加载它自己在搜寻路径 底下找到的 test.class 。接着在 test.class 之内有加载 testlib.class 的需求,由于 test.class 是由 AppClassLoader 所加载,所以 testlib.class 内 定是由 AppClassLoader 根据其搜寻路径来寻找,AppClassLoader 一样要请求其 Parent 先试着加载,但是 Bootstrap Loader 根本找不到 testlib.class( 被我 们删除了),所以回头转由 ExtClassLoader 来搜寻, ExtClassLoader 在其搜寻路径\lib\ext\classes 底下找到 testlib.class ,因此将它加载。最 后我们看到 test.class 由 AppClassLoader 载入, 而 testlib.class 则是由 ExtClassLoader 载入。 测试七: \classes 底下 \lib\ext\classes 底下 c:\myclasses 底下 test.class testlib.class 结果: C:\myclasses>java test https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@197d257 https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@197d257 从输出我们可以看出,当 AppClassLoader 要加载 test.class 时,先请其 Parent,也就是 ExtClassLoader 来载入,而 ExtClassLoader 又请求其 Parent,即 Bootstrap Loader 来载入 test.class 。但是 Bootstrap Loader 无法在其搜寻路径下找到 test.class( 被我们删 掉了), ExtClassLoader 只无法在其搜寻路径下找到 test.class( 被我们删掉了),最后只好由 AppClassLoader 加载它在自己搜寻路径底下找到的 test.class 。接着在 test.class 之内有加载 testlib.class 的需求,由于 test.class 是由 AppClassLoader 所加载,所以 testlib.class 内定是由 AppClassLoader 根据其搜寻 路径来寻找,但是 Bootstrap Loader 根本找不到 testlib.class( 被我们删除了) , ExtClassLoader 也找不到 testlib.class( 被我们删除了) ,所以回头转由 AppClassloader 来搜寻,AppClassLoader 在其搜寻路径底下找到 testlib.class , 因此将它加载。最后我们看到 test.class 和 testlib.class 都是由 AppClassLoader 载入。 总结: 各个 java 类由哪些 classLoader 加载? 1)java 类可以通过实例.g

etClass.getClassLoader()得知 2)接口由 AppClassLoader(System ClassLoader,可以由 ClassLoader.getSystemClassLoader() 获得实例)载入 3)ClassLoader 类由 bootstrap loader 载入 ClassLoader hierachy: jvm 建立->初始化动作->产生第一个 ClassLoader,即 bootstrap loader->bootstrap loader 在 https://www.360docs.net/doc/0715580564.html,uncher 类里面的 ExtClassLoader, 并设定其 Parent 为 null->bootstrap loader 载入 https://www.360docs.net/doc/0715580564.html,uncher$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 类时不成功则抛出 这个异常 如果安装 JDK1.3那么安装程序一定会同时安装两套 JRE。 一套位于 jdk\jre 目录 一套位于 program files\JavaSoft 目录 如果是 JDK 1.4可以选择是否安装 program files\java 目录下的 jre,但是 jdk 安装目录下的 jre 这套 jre 必须安装 JRE 与 PC 比较 JRE: java 类函数库>原生函数库.dll>JAVA 虚拟机(jvm.dll)>帮助函数库.dll PC: Win32 API .dll>CPU 编写好的 Java 源文件必须要有 JRE 才能帮助我们运行,Java 虚拟机只是 JRE 里的一个 成员而已,或者说 jvm 只是 jre 里头一个动态连接函数库, jdk 里面的 jre 一般用于运行 java 本身的程序,比如 javac,等等.programfiles 下面的 jre 用 于运行用户编写的 java 程序. JRE 下的 bin\client 或者 bin\server 的 jvm.dll 就是 JVM 了 ---------------------------在刚装好 jdk,没有对计算机进行任何设置时,进入命令行窗口 C:\Documents\Administrator>java -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(T

M) Client VM (build 1.5.0_11-b03, mixed mode, sharing) C:\Documents\Administrator>java -server -version Error: no `server' JVM at `C:\Program Files\Java\jre1.5.0_11\bin\server\jvm.dll' ----------------------------------当设置 path 路径中包含 jdk\bin 目录后 ---------------------------C:\>set path="C:\Program Files\Java\jdk1.5.0_11\bin"; %path%; C:\>java -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Client VM (build 1.5.0_11-b03, mixed mode, sharing) C:\>java -server -version java version "1.5.0_11" Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_11-b03) Java HotSpot(TM) Server VM (build 1.5.0_11-b03, mixed mode) -server 的-version 就可以显示出来了 ---------------------------JDK 里用 Java 所写的开发工具 如 javac.exe jar.exe 都存放在 JDK 安装目录\lib\tools.jar 这个档案中 javac.exe 只是一个包装器(wrapper),而制作目的是为了让开发者免于输入太长的指令。 实际等于: java -classpath x:\jdk1.xx\lib\tools.jar com.sun.tools.javac.Main 当用 j2me 开发 palm 应用程序的时候, 工具会帮我们打包 jar 档,然后用一个 RPC 档的 外壳罩住,让 java 程序看起来像是一个原生(native)的应用程序。 用.NET 开发出来的执行文件也是一个包装器的概念。 JDK 里面的工具几乎全是用 java 所写的,所以 JDK 本身就是 Java 应用程序,因此要用 JDK 附的工具来开发 Java 程序, 也必须要自行附一套 JRE 才行。这就是 JDK 安装目录\jre 下需要一套 JRE 的原因。 位于 program files\下的那套 JRE 就是拿来执行我们自己写的 java 应用程序。不过,两 套中任何一套 JRE 都可以拿来执行我们所撰写的 Java 应用程序, 可是 JDK 内附的开发工具在预设使用包装器(.exe) 来启动的情形下,都会自己去选用 \jre 底下那套 JRE。 -----------------------到底是执行哪一个 java.exe java xxx 当一台机器上有多个 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 后有一个验证程序,verify.dll 验证 jre 和 java.exe 的版本是否一致, 如果不一致则会发生错误 一般把常用的工具档放到 JDK 目录\jre\lib\ext 下 把有关安全机制的配置文件放到 JDK 目录\jre\lib\security 下 调用了其他 Java 函数库的程序,在编译阶段没有问题,可是却无法执行,显示 ClassNotFoundException 的原因可能是: 在 system32和 jdk\bin 目录下都有 java.exe 而 javac.exe 只有在 jdk\bin 目录下有 javac.exe 会自动调用 JDK 所在目录下的那套 JRE , 因此在编译时 JVM 会找到函数库, 所以编译不会发生问题,

但在执行时,键入 java xxx 的时候会优先执行 system32 下的 java.exe 因此会自动调用 program files 目录下的那套 JRE(稍后解释) 所以要执行就必须把外部 jar 文件放到相应 jre\lib\ext 目录下 JDK\jre\bin\下有两个目录 server,client 两个目录下都会有 jvm.dll client 目录下的 jvm.dll 较小 server 目录下的较大 ----------------------系统默认 path C:\Documents\Administrator>set path Path=C:\WINDOWS\system32; C:\WINDOWS; C:\WINDOWS\System32\Wbem PATHEXT=.COM; .EXE; .BAT; .CMD; .VBS; .VBE; .JS; .JSE; .WSF; .WSH 在 system32目录 底下找不到 JRE 目录, c:\windows 目录 也找不到 JRE 目录的情况 在 下,根据下一个逻辑,就是去查询注册表 C:\Program Files\Java\jre1.xx 该目录下的 bin 子目录却只有看到 client 子目录,却没有 看到 server 子目录。这就是为何在一开始执行 java -server -version 会报错的原因 -----------------------------------============================ ============================ JAVA 类加载器 ============================ ============================ 有了动态性, 我们的应用程序就可以在不用全盘重新编译的情况下更新系统, 或者在不 用停止主程序运作的情况下,除去系统中原有的 bug,或者是增加原本不具备的新功能。 一般来说,常见的程序语言先天上并不具有动态性的本质,如 C、C++本身就不具备动 态性。因此,为了让这些本身不具有动态性的程序语言具有某种程度的动态性, 就必须依赖底层的操作系统提供一些机制来实现动态性,Windows 操作系统底下的动 态联结函式库(Dynamic Linking Library) 和 Unix 底下的共享对象(Share Object) 要运用这些底层操作系统所提供的机制必须多费一些功夫来撰写额外的程序代码(例如 Windows 平台上需要使用 LoadLibrary() 与 GetProcAddress() , 两个 Win32 API 来完成动态性的需求),这些额外撰写的程序代码也会因为作业平台的 不同而不同,毕竟这些额外的程序代码与程序本身的运作逻辑甚少关联, 所以维护起来虽不算复杂,但仍有其难度。 每个类对 java 机来说, 都是一个独立的动态联结函数库, 只不过扩展名不是.dll 或.so 而 是.class 所以可以在不重新编译其他 java 程序代码的情况下,只修改需要修改的执行单位,并 放入文件系统中,等下次该 java 虚拟机重新启动时, 这个逻辑上的 Java 应用程序就会因为加载了新修改的.class 文件,自己的功能也做了更 新。这是一个最基本的动态性功能。 JSP/Servlet 之类的 Web Container 或者高档的 Application Server 里的 EJB Container 他 们会提供一个 Hot Deployment 功能。 即在不关闭 Web Server 的情况下,放入已编译好的新 Servlet 以取代旧的 Servlet,下次 Http request 时,就会自动释放旧的 servlet 而重载新的 servlet。

程序运行时需要的核心类库位于 jre\lib\rt.jar 中 类加载器的作用就是把类从表态的硬盘 .class 文件,复制一份到内存中,并做一此 始 化工作 java.exe 就是利用几个原则找到 JRE 后,把.class 直接转交给 JRE 运行后便功成身退 public class test { public static void main(String[] args) { System.out.println("Hello DD"); } } javac test.java java -verbose:class test -----------------------------------------------------classloader 的两种载入方式: 1)pre-loading 预先载入,载入基础类 2)load-on-demand 按需求载入 只有实例化一个类时,该类才会被 classloader 载入,仅仅申明并不会载入 基础类库是预先加载的(pre-loading) 用户所写的一般类是按需加载的(load-on-demand) 按需加载 三个类 public class A { public void print() { System.out.println("Using Class A"); } } public class B { public void print() { System.out.println("Using Class B"); } } public class Main { public static void main(String[] args) { A a=new A(); B b; a.print(); } } javac *.java java -verbose:class Main [Loaded Main from file:/C:/] [Loaded A from file:/C:/] Using Class A [Loaded https://www.360docs.net/doc/0715580564.html,ng.Shutdown from shared objects file] [Loaded https://www.360docs.net/doc/0715580564.html,ng.Shutdown$Lock from shared objects file] 没有看到 [Loaded Main from file:/C:/] [Loaded A from file:/C:/] [Loaded B from file:/C:/] -------------------------------------------- 动态加载的例子 三个类 public class Word { public void start() { System.out.println("Word start"); } } public class Excel { public void start() { System.out.println("Excel start"); } } public class Office { public static void main(String[] args) { if(args.length!=1) { return ; } if(args[0].equals("Word")) { Word w=new Word(); w.start(); }else if(args[0].equals("Excel")) { Excel e=new Excel(); e.start(); } } } 依需求加载的优点是节省内存,但是仍有其缺点。举例来说,当程序第一次用到该类别 的时候,系统就必须花一些额外的时间来加载该类别,使得整体执行效能受到影响, 尤其是由数以万计的类别所构成的 Java 程序。可是往后需要用到该类别时,由于类别 在初次加载之后就会被永远存放在内存之中,直到 Java 虚拟机关闭, 所以不再需要花费额外的时间来加载。 总的来说,就弹性上和速度上的考虑,如此的设计所带来的优点(弹性和省内存)远超过 额外加载时间的花费(只有第一次用到时),因此依需求加载的设计是明智的选择。 如果我们新增了 Access.java 和 PowerPoint.java 这两个新类别时, Office.java 里的主程序就必须增加两个 if … else 的循环 那么如何来更好的展示 java 在可扩展性的优势呢 ---------------------------------------------------------使 JAVA 程序更有动态性的方法有两种 1)implicit 隐式,即利用实例化才载入的特性来动态载入 class 2)explicit 显式方式,又分两种方式: 1)https://www.360docs.net/doc/0715580564.html,ng.Class

的 forName()方法 2)https://www.360docs.net/doc/0715580564.html,ng.ClassLoader 的 loadClass()方法 隐式的:new 关键字 生成类的实例 第一种方法: Class.forName() 加载类 一个接口 public interface Assembly { public void start() ; } 三个类 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 ; */ Assembly a = (Assembly) c.newInstance(); a.start() ; } } public class Word implements Assembly { public void start() { System.out.println("Word Start") } } public class Excel implements Assembly { public void start() { System.out.println("Excel Start") } } -------------------------------------------有两个 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,ClassLoader.getCallerClassLoader()); 而具有三个参数的 forName()方法,最后调用的是: forName0(name, initialize, loader); 关于名为 loader 这个参数的用法 public class Office { public static void main(String args[]) throws Exception { Class c = Class.forName(args[0],true,null) ; //line /*Object o = c.newInstance() ; Assembly a = (Assembly) o ; */ Assembly a = (Assembly) c.newInstance(); a.start() ; } } C:\>java Office Excel Exception in thread "main" https://www.360docs.net/doc/0715580564.html,ng.ClassNotFoundException: Excel at https://www.360docs.net/doc/0715580564.html,ng.Class.forName0(Native Method) at https://www.360docs.net/doc/0715580564.html,ng.Class.forName(Class.java:242) at Office.main(Office.java:9) //line 前加上一行,再修改改为 Office o=new Office(); Class c=Class.forName(args[0],true,o.getClass().getClassLoader()); 最终代码为 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); Class c = Class.forName(args[0],true,o.getClass().getClassLoader()) ; Assembly a = (Assembly) c.newInstance(); a.start() ; } } 就可以运行了 只有一个参数的 forName() 方法,由于在内部使用了 ClassLoader.getCallerClassLoader() 来取得加载呼叫他的类别所使用的类别加载器, 和我们自己写的程序有相同的效用。( 注意,ClassLoader.getCallerClassLoader()是一个 private 的方法,所以我们无法自行叫用, 因此必须要自己产生一个 Office 类别的实例,再去取得加载 Office 类别时所使用的类 别加载器)。 三个参数的 Class.forName()的第二参数 给类添加一个静态代码块 public class Word implements Assembly { static { System.out.println("Word Static Initialization"); } public void start() { System.out.println("Word start"); } } public class Office {

public static void main(String args[]) throws Exception { Office o=new Office(); System.out.println("类准备载入"); //Class c=Class.forName(args[0],true,o.getClass().getClassLoader()); Class c=Class.forName(args[0],false,o.getClass().getClassLoader()); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); } } 为 true 时 C:\>java Office Word 类准备载入 Word Static Initialization 准备实例化 Word start Word start 为 false 时 C:\>java Office Word 类准备载入 准备实例化 Word Static Initialization Word start Word start 静态初始化区块是在类别第一次被实例化的时候才会被呼叫那仅仅一次 注意: 不管您使用的是 new 来产生某类别的实例、或是使用只有一个参数的 forName()方法, 内部都隐含了”加载类别+呼叫静态初始化区块”的动作。 而使用具有三个参数的 orName()方法时,如果第二个参数给定的是 false,那么就只会 命令类别加载器加载该类别,但不会叫用其静态初始化区块, 只有等到整个程序第一次实例化某个类别时,静态初始化区块才会被叫用 static 块在什么时候执行? 1)当调用 forName(String)载入 class 时执行,如果调用 ClassLoader.loadClass 并不会执 行.forName(String,false,ClassLoader)时也不会执行. 2) 如 果 载 入 Class 时 没 有 执 行 static 块 则 在 第 一 次 实 例 化 时 执 行 . 比 如 new ,Class.newInstance()操作 3)static 块仅执行一次 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); System.out.println("类准备载入"); ClassLoader loader=o.getClass().getClassLoader(); Class c=loader.loadClass(args[0]); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); } } -----------------------------------------------第二种:直接使用 ClassLoader 类别的 loadClass() 方法来加载类 此方式只会把类加载至内存, 并不会调用该类别的静态初始化区块, 而必须等到第一次 实例化该类别时,该类别的静态初始化区块才会被叫用。 这种情形与使用 Class 类别的 forName()方法时, 第二个参数传入 false 几乎是相同的结 果。 另一种方式 public class Office { public static void main(String args[]) throws Exception { Office o=new Office(); Class co=Office.class; System.out.println("类准备载入"); ClassLoader loader=co.getClassLoader(); Class c=loader.loadClass(args[0]); System.out.println("准备实例化"); Assembly a1=(Assembly)c.newInstance(); a1.start(); Assembly a2=(Assembly)c.newInstance(); a2.start(); } } 归纳 1. Office o=new Office(); ClassLoader loader=o.getClass().getClassLoader(); 调用对象的 getClass()方式取得该对象的引用,再调用该引用的 getClassLoader()方法取得 该对象类加载器的引用 2. O

ffice o=new Office(); Class co=Office.class; ClassLoader loader=co.getClassLoader(); 直接定义一个此对象类的.class 类引用然后由此对象的 getClassLoader()方法取得该对 象类加载器的引用 然后 Class c=loader.loadClass(args[0]); Class 类的实例. >>Class 类无法手工实例化,当载入任意类的时候自动创建一个该类对应的 Class 的实例, >>某个类的所有实例内部都有一个栏位记录着该类对应的 Class 的实例的位置., >>每个 java 类对应的 Class 实例可以当作是类在内存中的代理人.所以当要获得类的信 息(如有哪些类变量,有哪些方法)时,都可以让类对应的 Class 的实例代劳。 java 的 Reflection 机制就大量的使用这种方法来实现 >>每个 java 类都是由某个 classLoader(ClassLoader 的实例)来载入的,因此 Class 类别的 实例中都会有栏位记录他的 ClassLoader 的实例,如果该栏位为 null, 则表示该类别是由 bootstrap loader 载入的(也称 root laoder),bootstrap loader 不是 java 所 写成,所以没有实例. ------------------------------------------------------自己建立类别加载器来加载类别 利用 Java 本身提供的 https://www.360docs.net/doc/0715580564.html,.URLClassLoader 如 实 例 化 一 个 URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader 优先找当前目录,再在 url 中找.class 加 载.URL 中别忘在最后加"/"表示目录 import https://www.360docs.net/doc/0715580564.html,.*; public class Office { public static void main(String[] args) throws Exception { if(args.length!=1) { return ; }else { URL u=new URL("http://share/"); URLClassLoader ucl=new URLClassLoader(new URL[]{u}); Class c=ucl.loadClass(args[0]); Assembly asm=(Assembly)c.newInstance(); asm.start(); } } } ------------------------------------------------ import https://www.360docs.net/doc/0715580564.html,.*; public class Office { public static void main(String[] args) throws Exception { URL u = new URL("Http://share/") ; URLClassLoader ucl = new URLClassLoader(new URL[]{ u }) ; Class c = ucl.loadClass(args[0]) ; Assembly asm = (Assembly) c.newInstance() ; asm.start() ; URL u1 = new URL("Http://share/") ; 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()) ; } } C:\>java Office Word Word Static Initialization Word start Word start https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 null null https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 null null https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoa

der@82ba41 https://www.360docs.net/doc/0715580564.html,uncher$AppClassLoader@82ba41 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() ----------------------------------------------- ----------------------------------------------一切都是由 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 和由哪个类别加载

相关主题
相关文档
最新文档