您的位置:首页 > 编程语言 > Java开发

Java深度历险

2008-11-19 16:09 274 查看
如果安装JDK1.3那么安装程序一定会同时安装两套JRE。
一套位于 jdk/jre目录
一套位于program files/JavaSoft目录
如果是JDK 1.4可以选择是否安装program files/java目录下的jre,但是jdk安装目录下的jre这套jre必须安装
JRE与PC比较
JRE: java类函数库>原生函数库.dll>J***A虚拟机(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会报错的原因
------------------------------------
============================
============================
J***A 类加载器
============================
============================
有了动态性,我们的应用程序就可以在不用全盘重新编译的情况下更新系统,或者在不用停止主程序运作的情况下,除去系统中原有的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 java.lang.Shutdown from shared objects file]
[Loaded java.lang.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在可扩展性的优势呢
----------------------------------------------------------
使J***A程序更有动态性的方法有两种
1)implicit隐式,即利用实例化才载入的特性来动态载入class
2)explicit显式方式,又分两种方式:
1)java.lang.Class的forName()方法
2)java.lang.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" java.lang.ClassNotFoundException: Excel
at java.lang.Class.forName0(Native Method)
at java.lang.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 本身提供的java.net.URLClassLoader
如实例化一个URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader优先找当前目录,再在url中找.class加载.URL中别忘在最后加"/"表示目录
import java.net.*;
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 java.net.*;
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
sun.misc.Launcher$AppClassLoader@82ba41
null
null
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$AppClassLoader@82ba41
null
null
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$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
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$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" java.lang.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
sun.misc.Launcher$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
sun.misc.Launcher$ExtClassLoader@7259da
sun.misc.Launcher$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
sun.misc.Launcher$ExtClassLoader@7259da
Exception in thread "main" java.lang.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 找的到testlib.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
sun.misc.Launcher$AppClassLoader@197d257
sun.misc.Launcher$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
sun.misc.Launcher$AppClassLoader@197d257
sun.misc.Launcher$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类可以通过实例.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类时不成功则抛出这个异常
如果安装JDK1.3那么安装程序一定会同时安装两套JRE。
一套位于 jdk/jre目录
一套位于program files/JavaSoft目录
如果是JDK 1.4可以选择是否安装program files/java目录下的jre,但是jdk安装目录下的jre这套jre必须安装
JRE与PC比较
JRE: java类函数库>原生函数库.dll>J***A虚拟机(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会报错的原因
------------------------------------
============================
============================
J***A 类加载器
============================
============================
有了动态性,我们的应用程序就可以在不用全盘重新编译的情况下更新系统,或者在不用停止主程序运作的情况下,除去系统中原有的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 java.lang.Shutdown from shared objects file]
[Loaded java.lang.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在可扩展性的优势呢
----------------------------------------------------------
使J***A程序更有动态性的方法有两种
1)implicit隐式,即利用实例化才载入的特性来动态载入class
2)explicit显式方式,又分两种方式:
1)java.lang.Class的forName()方法
2)java.lang.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" java.lang.ClassNotFoundException: Excel
at java.lang.Class.forName0(Native Method)
at java.lang.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 本身提供的java.net.URLClassLoader
如实例化一个URLClassLoader. URLClassLoader ucl = new URLClassLoader(new URL[]{new URL("file:/e:/bin/")}),URLClassLoader优先找当前目录,再在url中找.class加载.URL中别忘在最后加"/"表示目录
import java.net.*;
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 java.net.*;
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
sun.misc.Launcher$AppClassLoader@82ba41
null
null
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$AppClassLoader@82ba41
null
null
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$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
sun.misc.Launcher$AppClassLoader@82ba41
sun.misc.Launcher$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" java.lang.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
sun.misc.Launcher$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
sun.misc.Launcher$ExtClassLoader@7259da
sun.misc.Launcher$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
sun.misc.Launcher$ExtClassLoader@7259da
Exception in thread "main" java.lang.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 找的到testlib.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
sun.misc.Launcher$AppClassLoader@197d257
sun.misc.Launcher$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
sun.misc.Launcher$AppClassLoader@197d257
sun.misc.Launcher$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类可以通过实例.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类时不成功则抛出这个异常

http://www.cn-java.com/www1/?action-viewnewstemid-5836
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: