类与类加载器
对于任何一个类,都需要有加载它地类加载器和这个类本身一同确立在Java虚拟机中的唯一性,
每一个类加载器,都拥有一个独立的类名称空间。
比较两个类是否相等:
即使两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不相同。
相等是指:Class对象的equals()、isAssignableFrom()、isInstance()方法以及instanceof
双亲委任模型
从Java虚拟机的角度来讲,只存在两种不同的类加载器。
一种是启动类加载器(Bootstrap ClassLoader),用c++写的,是虚拟机自身的一部分
另外一种是其他类加载器,是由Java写的,独立于虚拟机外部。并且全部都继承自java.lang.ClassLoader
从Java开发人员的角度来讲,绝大部分Java程序都会使用以下3种系统提供的类加载器
--启动类加载器(BootStrap ClassLoader)
这个类加载器复制将存放在<JAVA_HOME>\lib目录中的能够被虚拟机识别的
(仅按照文件名识别,如rt.jar)类库加载到虚拟机内存中。
--扩展类加载器
--应用程序类加载器
类加载器之间的父子关系一般不会以继承关系来实现而是用组合关系来复用父类加载器
工作过程:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,
而是把这个请求委派给父类加载器去而完成,每一个层次的类加载器都是如此,
因此所有的加载请求最终都应该传送给顶层的启动类加载器中,
只有当父类加载器反馈自己无法完成这个加载请求时(它的搜索范围内没有找到所需要的类),
子加载器才会尝试自己去加载
使用双亲委任模型的好处:
Java类随着它的类加载器一起具备一种带有优先级的层次关系。
如java.lang.Object,它存放在rt.jar中,无论哪一个类加载器要加载这个类,
最终都是委任给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。
如果没有使用双亲委任模型,用户自己编写了一个java,lang,Object的类,并放在程序的Classpath下,
那么系统中将会出现多个不同的Object类。
模型的实现:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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;
}
}
先检查是否已经被加载过,若没有加载则调用父加载器的loadClass()方法,
若父类加载器为空则默认使用启动类加载器作为父类加载器。
如果父类加载失败,则抛出ClassNotFoundException异常,再调用自己的findClass()进行加载。
破坏双亲委任模型
启动类加载器 jre/lib/rt.jar
扩展类加载器 jre/lib/ext/
应用类加载器 classpath下
JNDI服务 (Java Naming and Directory Interface,Java命名和目录接口)是JAVA标准服务,JNDI服务的代码是由启动类加载器去加载的,但是JNDI的目的就是对资源进行集中管理和查找,他需要调用独立厂商实现并部署在应用程序的ClassPath下的JDNI接口提供者(SPI)的代码,但是启动类加载器不可能认识这些代码。
引入线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。默认就是应用类加载器。
JNDI服务使用这个线程上下文加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这种行为实际上就是打通了双亲委任模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委任模型的一般性原则。