JVM是如何知道java.lang包中的类的?JVM又是如何知道我们应用中的类的?我们的应用中明明是有某个类, 但是JVM却抛出ClassNotFoundException,这是为什么?XxxImpl类已经实现了接口Xxx,但是却抛出XxxImpl does not extend from Xxx,这是为什么?使用类型转换时,可能会抛出 aa.bb.cc.XXX can not cast to aa.bb.cc,这又是为什么?等等诸多看似诡异情况,其实都是因为ClassLoader。
了解反射的人都知道每一个类都有一个Class对象与之对应,那么这个Class对象又是哪来的呢?
如果要了解Class的基本知识,可以参考:
http://blog.csdn.net/irelandken/article/details/7048817 http://tyrion.iteye.com/blog/1958814 http://www.blogjava.net/lihuaxajh/articles/94371.html http://www.javaworld.com/article/2077332/core-java/get-a-load-of-that-name.html http://www.javaworld.com/article/2075796/java-platform/java-101--class-and-object-initialization.html
我对ClassLoader的理解
下面是我看了这些博客、文章后,并自定义了一个ClassLoader进行测试后的理解:
1)ClassLoader加载一个类时,不会加载类中所有的类,而是在运行时根据要使用的类动态加载的,即按需加载。
2)ClassLoader加载一个类时,也会加载其父类(包括接口)
3)当前类A在运行时,类中要使用的其它的类(B,C,D类等),默认是由当前类A的加载器来加载的。
4)类型强制转换时,先判定是不是同一个类加载器,如果不是,就不能进行转换。一个对象A但往由BootstrapCLassLoader加载的类(A的父类或者接口)转换时,好像不遵循这个过程。
5)Thread#contextClassLoader可以用于在切换类加载器。
6)默认情况下,线程的上下文加载器采用的与父线程的上下文加载器是同一个。
7)Class.forName(“xxx”)、Xxx.class 的加载器,使用的都是当前类的类加载器。
下面是自定义类加载器
代码清单:
下面就对这些类做一个简单的说明:
StringUtil.java
StringUtil中只有两个方法,用于判断字符串空值。写这个类实在是没有必要,但是在测试中也可以看到效果的。
DebugUtil.java
只是为了打出一些信息。
Person.java
这是一个典型的Java Bean,没有什么可说的。
ClassLoaderTestRunner.java
这是业务类。从代码上看也是很简单的,只是加载了Person类,创建一个Person对象,输出一些信息。
这个类 既可以使用系统默认的类加载器加载测试(main方法),也可以使用自定义的类加载器来测试。
自定义ClassLoader
目前常用的加载机制有两种:委托加载机制、子类优先加载
1)委托加载机制loadClass()的流程:
(1) 判断是否已经加载这个类
(2) 如果没有加载,当前类加载器的父加载器加载,即执行loadClass()方法。
(3) 如果当前类加载器的所有父加载器都没有加载到就让当前加载器调用 findClass()加载。
2)子类优先加载器的流程:
(1) 判断是否已经加载这个类
(2) 当前类加载器直接加载类
(3) 加载不到才走父类加载流程。
JDK中默认的加载方式是1),也就是委托加载机制。
自定义类加载器,一般会重写loadClass、findClass。
delegate字段是用于指定是否使用默认的委托加载机制。
这是自定义的加载类的方法。流程是:
1)判断该加载器是否已经加载了这个类,为了避免重复的加载。
2)如果delegate=true,委托加载,就使用默认的加载方式。
3)如果delegate=false, 排除受保护的类,然后使用下面的自定义的findClass来加载类。上述过程如果出现了异常,仍然会使用默认的加载方法。
findClass是根据类名加载并定义一个Class对象。这个方法写的很粗糙,只是为了测试ClassLoader。
接下来就可以使用ClassLoaderTest.java来测试了:
关于这个测试的简单说明:
1)这个测试中的相关类,如String, UrlClassLoader, ClassLoader,Object, Thread,Runnable,Exception等都是由默认的系统加载器(SystemClassLoader)或者其父类(ExtClassLoader、BootstrapClassLoader)来加载的。当前这个运行时,这个类(ClassLoaderTest)的加载器是SystemClassLoader。
2)加载ClassLoaderTestRunner类时,使用的自定义的类加载器(也就是SystemClassLoader的子加载器)。加载ClassLoaderTestRunner类时,还会加载其父类,也就是Object类和Runable接口。
3)这个测试执行的过程:
(1) 创建一个自定义加载器实例,指定其父加载器为系统加载器。
(2) 加载类ClassLoaderTestRunner。
(3) 启动一个线程执行相关任务。
4)根据上述所说,如果将Object task = myAppClassLoader .loadClass( classFullName) . newInstance();
改为:
ClassLoaderRunner task= (ClassLoaderRunner) myAppClassLoader. loadClass( classFullName ) . newInstance();就会出错。
ClassLoaderRunner 是系统加载器加载的,myAppClassLoader是一个自定义加载器。所以两者不能进行类型的转换。
如果直接在这个类中使用自定义类加载器来加载Person类,也会出现错误。
如果有兴趣的话,可以根据我上面总结的内容,已经我的这个测试,自己分析一下子线程t执行过程中,哪些类是被自定义的类加载器加载的,哪些类是被系统加载器加载的,上述task执行的结果是什么?
如果有不理解的也可以参考:
参考
http://blog.csdn.net/irelandken/article/details/7048817
http://tyrion.iteye.com/blog/1958814
http://www.blogjava.net/lihuaxajh/articles/94371.html
http://www.javaworld.com/article/2077332/core-java/get-a-load-of-that-name.html