类加载器
通过一个类的全限定的名来获取此类的二进制字节流这个动作在java虚拟机外部实现,让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块成为类加载器。
类与类加载器
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在java虚拟机中的唯一性;比较两个类相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。
双亲委派
对于java虚拟机而言,只有两种类加载器,一种是启动类加载器,由c++语言实现,是虚拟机自身的一部分;另一种就是所有的其他类加载器,独立与虚拟机外部,都集成自抽象类java.lang.ClassLoader。
对于开发人员,类加载器还可以划分的更细一些,大部分java程序都会使用以下三种系统提供的类加载器。
启动类加载器
这个类加载器负责将存放在<JAVA_HOME>\lib目录中,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按文件名识别,如rt.jar)类库加载到虚拟机中。启动类加载器无法被java程序直接引用。
扩展类加载器<Extension ClassLoader>
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中所有类库,开发者可以直接使用扩展了类加载器。
应用程序类加载器<Application ClassLoader>
这个类加载器由sun.misc.Lanuncher$ExtClassLoader实现。由于这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般称它为系统类加载器。它负责加载用户类路径上所指定的类库,一般情况下这个就是程序中默认的类加载器。
我们的应用程序都是由这3种类加载器相互配合进行加载的,如果有必要,还可以加入自己订单已的类加载器。类加载器之间的关系一般如图
类加载器之间的这种层次关系,称为类加载器的双亲委派模型。双亲委派模型要求除了顶层启动类加载器外,其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系使用组合关系实现。这种模型并不是强制的约束模型,而是java设计者推荐给开发者的一种类加载器的实现。
双亲委派模型的工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给自己的父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需要的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型来组织加载器之间的好处时java类随着它的加载器一起具备了带有优先级的层次关系。例如类java.lang.Object,它存放在rt.java之中,无论哪一个类加载器要加载这个类,最终都会委派给处于最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反如果没有使用双亲委派模型,如果用户自己编写了一个成为java.lang.Object的类,并放在程序的CalssPath中,那系统将会出现多个不同的Object类。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先检查是否已经加在过了
Class<?> c = findLoadedClass(name);
if (c == null) {
//没有找到走加载
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加载器不为空调用父加载器
c = parent.loadClass(name, false);
} else {
//父加载器为空调用根加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
//父加在器无法加载
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
//父加载器没法加载,调用自己的加载方法
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
双亲委派模型的加载过程主要有以下四步:
1.检查是否已经被加载过
2.若是没有被加载过调用父加载器加载
3.若是父加载器为null使用启动类加载器加载
4.如果父类加载失败,调用自己的加载方法加载
破坏双亲委派模型
第一次被破坏时在双亲委派模型出来之前,也就是在jdk1.2之前
第二次被破坏是为了解决基础的类要调用回用户代码的情况,典型的例子是JNDI服务。JNDI是java的标准服务,它的代码由启动类加载器加载,但是JNDI需要调用由独立厂商实现并部署在应用程序的ClassPath下的JNDI接口提供者的代码,启动类加载器不可能认识这些代码,于是便有了线程上下文类加载器。这个类加载器通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将从父线程继承一个,如果全局范围内都没有设置的话,那这个类加载器默认就是应用程序类加载器。JNDI使用这个类加载器请求子类加载器去完成加载动作,这种行为实际上就是打通双亲委派模型的层次结构来逆向使用类加载器。
第三次被破坏是由于用户对程序动态性追求导致的,也就是代码热替换、模块化部署,也就是OSGi,OSGi实现模块化部署的关键则是它定义的类加载机制,每一个程序模块都有一个自己的类加载器,当需要更换一个模块时,就把模块联通类加载器一同换掉以实现代码的热替换。
在OSGi环境下,类加载器不再是双亲委派模型的的树状结构,而是复杂的网状结构,OSGi将按照下面的顺序进行类搜索。
1)将以java.*开头的类委派给父类加载器加载。
2)否则,将委派列表名单内的类委派给父类加载器加载。
3)否则,将Import列表中的类委派给 Export这个类的Bundle 的类加载器加载。
4)否则,查找当前Bundle 的ClassPath,使用自己的类加载器加载。
5)否则,查找类是否在自己的 Fragment Bundle中,如果在,则委派给 Fragment Bundle的类加载器加载。
6)否则,查找 Dynamic Import 列表的 Bundle,委派给对应Bundle的类加载器加载。
7)否则,类查找失败。
每天5分钟,您看着不累!剩余部分解析内容下节分享!