理解类加载器
转载请注明: http://blog.csdn.net/HEL_WOR/article/details/51287786
5个月前写了第一篇博客就是类加载器,这两天在common-pool2中的驱逐逻辑里遇到了对上下文类加载器的使用,觉得需要重写写一次
类记载器通俗来理解就是用来加载class文件,当需要创建一个对象时,都会使用到类加载器,对于继承自ClassLoader抽象类的子类加载器实现加载功能都是调用defineClass来完成的,这点在URLClassLoader中重写的findClass方法中可以看见。
平时我们在编程中能直接接触到的类加载器有系统类加载器,当前类加载器,线程上下文加载器。
1.对于一个我们创建的一个控制台程序,对于main方法所在类是用哪个类加载器来完成的?
其是由AppClassLoader完成的,可以debug进源码跟着就能发现。
2.为什么会使用AppClassLoader类加载器来进行加载工作,不是还有扩展类加载器,启动类加载器么?
对于双亲委派模型,其限制了每个类加载器可能加载的class文件路径,也规定了父子关系和加载顺序。对于我们的main方法所在类,当其通过编译产生class文件,是被放在类路径上的,而这个地址只能由AppClassLoader类加载器发现,对于扩展类加载器,引导类加载器,它们是看不到这个路径的。
在源码里:
AppClassLoader的加载路径:
ExtClassLoader的加载路径:
BootstrapClassLoader加载路径:
其中AppClassLoader,ExtClassLoader是URLClassLoader的内部类继承自ClassLoader,BootstrapClassLoader是JVM内嵌的,由C++实现。
光看ClassLoader这一段双亲委派的源码,读上去似乎比较好理解。
即:
1.加锁。
2.判断是否已被加载.
3.否,则交给父类加载
4.父类加载失败,交给启动类加载
5.启动类加载失败,交给子类加载
看上去比较简单的步骤,那么,问几个问题:
1.即然在 catch (ClassNotFoundException e) {}这段没有做任何操作,为什么还需要这段try..catch..,仅仅是为了吃掉异常?
2.交给子类去加载,这段逻辑是如何描述的?
3.是否看出了递归?
我们debug源码来解释上面提到递归的原因:
步骤一:初始使用AppClassLoader调用其重写的loadClass方法进行加载:
除非之前的AppClassLoader搜索路径已被保存且能从之中找到需要被加载的Class文件,否则AppClassLoader委托其父类ExtClassLoader加载。
步骤二:对于ClassLoader类中的loadClass方法,只有在URLClassLoader的内部类AppClassLoader中进行了重写,因此对于其父类ExtClassLoader,调用器loadClass方法,其实是在调用ClassLoader类的loadClass方法,即自己调用了自己。
可以看见,当前类加载器仍是AppClassLoader。其在委托其父类ExtClassLoader进行加载。
步骤三:
c = parent.loadClass(name, false);
此时parent是ExtClassLoader,继续调用其父类返回BootstrapClassLoader,由于BootstrapClassLoader是内嵌于JVM底层用C++描述的,因此这个用null表示,在其后的一步代码:
c = findBootstrapClassOrNull(name);
当判断得知parent为null后,会执行这步代码,即交给BootstrapClassLoader加载。这也是我们在一些书里看到的如果想把类委托给BootstrapClassLoader加载,则直接对指定类加载器为null即可。
启动程序后,对类的加载,都是从AppClassLoader开始的。
原因是,在代码编译链接完成后,首次加载时会调用getSystemClassLoader方法,其返回AppClassLoader。
上面描述得是子类到父类的委托过程 ,下面描述当父类无法加载时由子类自行加载的过程。
其实现过程用到了上面已进入的递归,try…catch…,findClass方法。
还是用这份代码:
try..catch…是为ExtClassLoader和AppClassLoader切换而准备的。
此时目标class文件已交由引导类加载器加载,当前加载失败后,由当前类加载器ExtClassLoader继续调用findClass方法。
ucp描述的是ExtClassLoader的查找路径,当在路径里没有找到目标class文件,会抛出ClassNotFoundException异常,随后此方法被递归栈的下一层捕获,当前方法出栈,类加载器被切换为AppClassLoader。
可以看到此时类加载器已变为了AppClassLoader。
此时再调用findClass方法,需要查找的路径改变:
以上就是父类加载失败由子类加载的过程。
我们来测试一下类加载的过程,按照上面的代码逻辑,AppClassLoader会先委托ExtClassLoader进行加载。那么我们将需要查找的地址当前根目录下,但将目标Aop文件放在java.lib.ext中,按照双亲委派原则,目标class文件的类加载器应该是ExtClassLoader而不是AppClassLoader,即使我们指定在类路径下查找。
Main类在当前根目录下而不会在java.lib.ext下,因此其会被AppClassLoader找到并加载。
但是,上面的加载逻辑会出现一个问题。
对于一些核心接口,我们需要用到第三方的类实现,但是根据双亲委派的原则,类的加载只能向上委托,核心接口位于核心类中,由启动类加载器加载,而第三方实现类放置于类路径上或者扩展路径上,我们需要向下委派AppClassLoader或者ExtClassLoader去其路径上搜索目标class文件并加载。
这就线程上下文类加载器出现的原因。线程上下文类加载器如果不设置,会默认使用AppClassLoader作为加载器。当我们需要逆向的去加载类,
我们设置设置当前线程的上下文类加载器,由当前线程去加载目标类。