当JVMQJava虚拟机)启动Ӟ会Ş成由三个cd载器l成的初始类加蝲器层ơ结构: bootstrap classloader | extension classloader | system classloader bootstrap classloader Q引|也称为原始)cd载器Q它负责加蝲Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或? D选项指定sun.boot.class.pathpȝ属性值可以指定附加的cR这个加载器的是非常Ҏ的,它实际上不是 java.lang.ClassLoader的子c,而是由JVM自n实现的。大家可以通过执行以下代码来获得bootstrap classloader加蝲了那些核心类库: URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls.toExternalform()); } 在我的计机上的l果为: 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/rt.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/jce.jar 文g:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar 文g:/C:/j2sdk1.4.1_01/jre/classes q时大家知道了ؓ什么我们不需要在pȝ属性CLASSPATH中指定这些类库了吧,因ؓJVM在启动的时候就自动加蝲它们了?br /> extension classloader Q扩展类加蝲器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirspȝ属性指定的Q中JAR的类包。这为引入除Java核心cM外的新功能提供了一个标准机制。因为默认的扩展目录Ҏ有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JARcdҎ有的JVM和system classloader都是可见的。在q个实例上调用方法getParent()Lq回I值nullQ因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码Ӟ System.out.println(System.getProperty("java.ext.dirs")); ClassLoader extensionClassloader=ClassLoader.getSystemClassLoader().getParent(); System.out.println("the parent of extension classloader : "+extensionClassloader.getParent()); l果为: C:\j2sdk1.4.1_01\jre\lib\ext the parent of extension classloader : null extension classloader是system classloader的parentQ而bootstrap classloader是extension classloader的parentQ但它不是一个实际的classloaderQ所以ؓnull?br /> system classloader Q系l(也称为应用)cd载器Q它负责在JVM被启动时Q加载来自在命ojava中的-classpath或者java.class.pathpȝ属性或?CLASSPATH操作pȝ属性所指定的JARcd和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()扑ֈ该类加蝲器。如果没有特别指定,则用戯定义的Q何类加蝲器都该cd载器作ؓ它的父加载器。执行以下代码即可获得: System.out.println(System.getProperty("java.class.path")); 输出l果则ؓ用户在系l属性里面设|的CLASSPATH?br />classloader 加蝲cȝ的是全盘负责委托机制。所谓全盘负责,x当一个classloader加蝲一个Class的时候,q个Class所依赖的和引用的所?Class也由q个classloader负责载入Q除非是昑ּ的用另外一个classloader载入Q委托机制则是先让parentQ父Q类加蝲?(而不是superQ它与parent classloadercM是承关p?LQ只有在parent找不到的时候才从自qc\径中d找。此外类加蝲q采用了cache机制Q也是如果 cache中保存了q个Classq接返回它Q如果没有才从文件中d和{换成ClassQƈ存入cacheQ这是Z么我们修改了Class但是必须重新启动JVM才能生效的原因?br /> 每个ClassLoader加蝲Class的过E是Q?br />1.此Class是否载入q(卛_cache中是否有此ClassQ,如果有到8,如果没有? 2.如果parent classloader不存在(没有parentQ那parent一定是bootstrap classloader了)Q到4 3.hparent classloader载入Q如果成功到8Q不成功? 4.hjvm从bootstrap classloader中蝲入,如果成功? 5.LClass文gQ从与此classloader相关的类路径中寻找)。如果找不到则到7. 6.从文件中载入ClassQ到8. 7.抛出ClassNotFoundException. 8.q回Class. 其中5.6步我们可以通过覆盖ClassLoader的findClassҎ来实现自q载入{略。甚臌盖loadClassҎ来实现自q载入q程?br /> cd载器的顺序是Q?br />先是bootstrap classloaderQ然后是extension classloaderQ最后才是system classloader。大家会发现加蝲的Class是重要的越在靠前面。这样做的原因是Z安全性的考虑Q试惛_果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这U委托机制保证了用户即h一个这Lc,也把它加入到了类路径中,但是它永q不会被载入Q因个类L由bootstrap classloader来加载的。大家可以执行一下以下的代码Q?br /> System.out.println(System.class.getClassLoader()); 会看到l果是nullQ这p明java.lang.System是由bootstrap classloader加蝲的,因ؓbootstrap classloader不是一个真正的ClassLoader实例Q而是由JVM实现的,正如前面已经说过的?br /> 下面p我们来看看JVM是如何来为我们来建立cd载器的结构的Q?br />sun.misc.LauncherQ顾名思义Q当你执行java命o的时候,JVM会先使用bootstrap classloader载入q初始化一个LauncherQ执行下来代码: System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader()); l果为: the Launcher's classloader is null (因ؓ是用bootstrap classloader加蝲,所以class loader为null) Launcher 会根据系l和命o讑֮初始化好class loaderl构QJVMq它来获得extension classloader和system classloader,q蝲入所有的需要蝲入的ClassQ最后执行java命o指定的带有静态的mainҎ的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoadercȝ一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoadercȝ一个实例。ƈ且都?java.net.URLClassLoader的子cR?br /> 让我们来看看Launcher初试化的q程的部分代码?br /> Launcher的部分代码: public class Launcher { public Launcher() { ExtClassLoader extclassloader; try { //初始化extension classloader extclassloader = ExtClassLoader.getExtClassLoader(); } catch(IOException ioexception) { throw new InternalError("Could not create extension class loader"); } try { //初始化system classloaderQparent是extension classloader loader = AppClassLoader.getAppClassLoader(extclassloader); } catch(IOException ioexception1) { throw new InternalError("Could not create application class loader"); } //system classloader讄成当前线E的context classloaderQ将在后面加以介l) Thread.currentThread().setContextClassLoader(loader); ...... } public ClassLoader getClassLoader() { //q回system classloader return loader; } } extension classloader的部分代码: static class Launcher$ExtClassLoader extends URLClassLoader { public static Launcher$ExtClassLoader getExtClassLoader() throws IOException { File afile[] = getExtDirs(); return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile)); } private static File[] getExtDirs() { //获得pȝ属性“java.ext.dirs?br /> String s = System.getProperty("java.ext.dirs"); File afile[]; if(s != null) { StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator); int i = stringtokenizer.countTokens(); afile = new File; for(int j = 0; j < i; j++) afile[j] = new File(stringtokenizer.nextToken()); } else { afile = new File[0]; } return afile; } } system classloader的部分代码: static class Launcher$AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(ClassLoader classloader) throws IOException { //获得pȝ属性“java.class.path?br /> String s = System.getProperty("java.class.path"); File afile[] = s != null ? Launcher.access$200(s) : new File[0]; return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader)); } } 看了源代码大家就清楚了吧Qextension classloader是用系l属性“java.ext.dirs”设|类搜烦路径的,q且没有parent。system classloader是用系l属性“java.class.path”设|类搜烦路径的,q且有一个parent classloader。Launcher初始化extension classloaderQsystem classloaderQƈsystem classloader讄成ؓcontext classloaderQ但是仅仅返回system classloaderlJVM?br /> q里怎么又出来一个context classloader呢?它有什么用呢?我们在徏立一个线EThread的时候,可以个线E通过setContextClassLoaderҎ来指定一个合适的classloader作ؓq个U程的context classloaderQ当此线E运行的时候,我们可以通过getContextClassLoaderҎ来获得此context classloaderQ就可以用它来蝲入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前U程的context classloaderQ而这个context classloader可以是它的子classloader或者其他的classloaderQ那么父classloader可以从其获得所需?ClassQ这打破了只能向父classloaderh的限制了。这个机制可以满_我们的classpath是在q行时才定,q由定制?classloader加蝲的时?由system classloader(卛_jvm classpath?加蝲的class可以通过context classloader获得定制的classloaderq加载入特定的class(通常是抽象类和接?定制的classloader中是其实?,例如web应用中的servlet是用这U机制加载的. 好了Q现在我们了解了classloader的结构和工作原理Q那么我们如何实现在q行时的动态蝲入和更新呢?只要我们能够动态改变类搜烦路径和清除classloader的cache中已l蝲入的Classp了,有两个方案,一是我们承一个classloaderQ覆盖loadclassҎQ动态的LClass文gq用defineClassҎ来;另一个则非常单实用,只要重新使用一个新的类搜烦路径来new一个classloaderp了,q样xCcL索\径以便来载入新的ClassQ也重新生成了一个空白的cache(当然,cL索\径不一定必L?。噢Q太好了Q我们几乎不用做什么工作,java.netURLClassLoader正是一个符合我们要求的classloaderQ我们可以直接用或者承它可以了Q?br /> q是j2se1.4 API的doc中URLClassLoader的两个构造器的描qͼ URLClassLoader(URL[] urls) Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader. URLClassLoader(URL[] urls, ClassLoader parent) Constructs a new URLClassLoader for the given URLs. 其中URL[] urls是我们要设|的cL索\径,parent是q个classloader的parent classloaderQ默认的是system classloader?br /> 好,现在我们能够动态的载入Class了,q样我们可以利用newInstanceҎ来获得一个Object。但我们如何此Object造型呢?可以此Object造型成它本n的Class吗? 首先让我们来分析一下java源文件的~译Q运行吧Qjavac命o是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compileҎ来编译: public static int compile(String as[]); public static int compile(String as[], PrintWriter printwriter); q回0表示~译成功Q字W串数组as则是我们用javac命o~译时的参数Q以I格划分。例如: javac -classpath c:\foo\bar.jar;. -d c:\ c:\Some.java 则字W串数组as为{"-classpath","c:\\foo\\bar.jar;.","-d","c:\\","c:\\Some.java"}Q如果带有PrintWriter参数Q则会把~译信息出到q个指定的printWriter中。默认的输出是System.err?br /> 其中 Main是由JVM使用Launcher初始化的system classloader载入的,Ҏ全盘负责原则Q编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入Q如果system classloader不能载入某个ClassӞ~译器将抛出一个“cannot resolve symbol”错误?br /> 所以首先编译就通不q,也就是编译器无法~译一个引用了不在CLASSPATH中的未知Class的java源文Ӟ而由于拼写错误或者没有把所需cd攑ֈCLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧Q?br /> 其次Q就是我们把q个Class攑ֈ~译路径中,成功的进行了~译Q然后在q行的时候不把它攑օ到CLASSPATH中而利用我们自q classloader来动态蝲入这个ClassQ这时候也会出现“java.lang.NoClassDefFoundError”的q例Qؓ什么呢Q?br /> 我们再来分析一下,首先调用q个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,Ҏ全盘负责原则Q当我们q行造型的时候,JVM也会使用system classloader来尝试蝲入这个Class来对实例q行造型Q自然在system classloaderL不到q个Class时就会抛出“java.lang.NoClassDefFoundError”的q例?br /> OKQ现在让我们来ȝ一下,java文g的编译和Class的蝲入执行,都是使用Launcher初始化的system classloader作ؓc蝲入器的,我们无法动态的改变system classloaderQ更无法让JVM使用我们自己的classloader来替换system classloaderQ根据全盘负责原则,限制了~译和运行时Q我们无法直接显式的使用一个system classloaderL不到的ClassQ即我们只能使用Java核心cdQ扩展类库和CLASSPATH中的cd中的Class?br /> q不dQ再试一下这U情况,我们把这个Class也放入到CLASSPATH中,让system classloader能够识别和蝲入。然后我们通过自己的classloader来从指定的class文g中蝲入这个ClassQ不能够委托 parent载入Q因样会被system classloader从CLASSPATH中将其蝲入)Q然后实例化一个ObjectQƈ造型成这个ClassQ这样JVM也识别这个ClassQ因?system classloader能够定位和蝲入这个Class从CLASSPATH中)Q蝲入的也不是CLASSPATH中的q个ClassQ而是?CLASSPATH外动态蝲入的Q这h行了吧Q十分不q的是,q时会出现“java.lang.ClassCastException”违例?br /> Z么呢Q我们也来分析一下,不错Q我们虽然从CLASSPATH外用我们自qclassloader动态蝲入了q个ClassQ但它的实例造型的时候是JVM会用system classloader来再ơ蝲入这个ClassQƈ试用我们的自己的classloader载入的Class的一个实例造型为system classloader载入的这个ClassQ另外的一个)。大家发C么问题了吗?也就是我们尝试将从一个classloader载入的Class的一个实例造型为另外一个classloader载入的ClassQ虽然这两个Class的名字一P甚至是从同一个class文g中蝲入。但不幸的是JVM 却认个两个Class是不同的Q即JVM认ؓ不同的classloader载入的相同的名字的ClassQ即使是从同一个class文g中蝲入的Q是不同的!q样做的原因我想大概也是主要Z安全性考虑Q这样就保证所有的核心Javac都是system classloader载入的,我们无法用自qclassloader载入的相同名字的Class的实例来替换它们的实例?br /> 看到q里Q聪明的读者一定想C该如何动态蝲入我们的ClassQ实例化Q造型q调用了吧! 那就是利用面向对象的基本Ҏ之一的多形性。我们把我们动态蝲入的Class的实例造型成它的一个system classloader所能识别的父类p了!q是Z么呢Q我们还是要再来分析一ơ。当我们用我们自qclassloader来动态蝲入这我们只要把这个Class的时候,发现它有一个父cClassQ在载入它之前JVM先会载入q个父类ClassQ这个父cClass是system classloader所能识别的Q根据委托机Ӟ它将由system classloader载入Q然后我们的classloader再蝲入这个ClassQ创Z个实例,造型个父cClassQ注意了Q造型成这个父c?Class的时候(也就是上溯)是面向对象的java语言所允许的ƈ且JVM也支持的QJVM׃用system classloader再次载入q个父类ClassQ然后将此实例造型个父cClass。大家可以从q个q程发现q个父类Class都是?system classloader载入的,也就是同一个class loader载入的同一个ClassQ所以造型的时候不会出CQ何异常。而根据多形性,调用q个父类的方法时Q真正执行的是这个ClassQ非父类 ClassQ的覆盖了父cL法的Ҏ。这些方法中也可以引用system classloader不能识别的ClassQ因为根据全盘负责原则,只要载入q个Class的classloaderx们自己定义的 classloader能够定位和蝲入这些Classp了?br /> q样我们可以事先定义好一l接口或者基cdƈ攑օCLASSPATH中,然后在执行的时候动态的载入实现或者承了q些接口或基cȝ子类。还不明白吗Q让我们来想一想Servlet吧,web application server能够载入Ml承了Servlet的Classq正的执行它们Q不它实际的Class是什么,是都把它们实例化成Z个Servlet ClassQ然后执行Servlet的initQdoPostQdoGet和destroy{方法的,而不这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(卛_制的classloader)动态蝲入。说了这么多希望大家都明白了。在applet,ejb{容器中,都是采用了这U机? 对于以上各种情况Q希望大家实际编写一些example来实验一下?br /> 最后我再说点别的, classloader虽然UCؓcd载器Q但q不意味着只能用来加蝲ClassQ我们还可以利用它也获得囄Q音频文件等资源的URLQ当Ӟq些资源必须在CLASSPATH中的jarcd中或目录下。我们来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描q吧Q?br /> public URL getResource(String name) 用指定的名字来查找资源,一个资源是一些能够被class代码讉K的在某种E度上依赖于代码位置的数据(囄Q音频,文本{等Q?br /> 一个资源的名字是以'/'号分隔确定资源的路径名的?br /> q个Ҏ先hparent classloader搜烦资源Q如果没有parentQ则会在内置在虚拟机中的classloaderQ即bootstrap classloaderQ的路径中搜索。如果失败,q个Ҏ调用findResource(String)来寻找资源?br /> public static URL getSystemResource(String name) 从用来蝲入类的搜索\径中查找一个指定名字的资源。这个方法用system class loader来定位资源。即相当于ClassLoader.getSystemClassLoader().getResource(name)?br /> 例如Q?br /> System.out.println(ClassLoader.getSystemResource("java/lang/String.class")); 的结果ؓQ?br /> jar:文g:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class 表明String.class文g在rt.jar的java/lang目录中?br />因此我们可以图片等资源随同Class一同打包到jarcd中(当然Q也可单独打包这些资源)q添加它们到class loader的搜索\径中Q我们就可以无需兛_q些资源的具体位|,让class loader来帮我们L了! |