引言
前面谈到了类加载机制,其中第一阶段“加载”,完成的工作是通过类的全限定名获取类的二进制字节流,然后把二进制字节流代表的静态存储结构转化成方法区运行时数据结构,再在方法区中生成一个Class对象,作为这个类对外的访问接口。
而完成“通过类的全限定名获取这个类的二进制字节流”这部分工作的代码块成为类加载器
类与类加载器
引言中提到了类加载器的定义,类加载器用于完成类的加载动作。对于任意一个类,在同一个虚拟机中,它可能有不同的类加载器加载,那么这两个类也不是“相等的”,这个类本身+加载它的类加载器一起,才能确定在Java虚拟机的唯一性。举个例子:
public class MyClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myClassLoader = new ClassLoader(){
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(null == is){
return super.loadClass(name);
}
byte[] bytes = new byte[is.available()];
is.read(bytes);
return defineClass(name,bytes,0, bytes.length);
}catch (IOException e){
throw new ClassNotFoundException(name);
}
}
};
Object obj = myClassLoader.loadClass("com.abc.MyClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof MyClassLoaderTest);
}
}
执行结果如下:
这里构造了一个简单的类加载器,使用这个类加载器去加载一个名为“com.abc.MyClassLoaderTest”的类,并实例化出一个对象,从执行结果看,这个对象的确是com.abc.MyClassLoaderTest这个类实例化出来的,但是这个对象与这个类做 instance of类型检查的时候却返回false,这是因为在虚拟机中存在两个MyClassLoaderTest类,一个是由系统应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的,虽然都来自同一个Class文件,但是它们依然是两个独立的类。
双亲委派模型
对于虚拟机来说,只存在两种不同的类加载器:一种是启动类加载器(BootStrap ClassLoader),另一种是除启动类加载器之外的所有其他类加载器。启动类加载器是虚拟机自身的一部分,由C++语言实现;其他类加载器独立于虚拟机外,由Java语言实现,并且全部继承自java.lang.ClassLoader
对于程序猿而言,类加载器可以分的更细致一些:
启动类加载器(BootStrap ClassLoader):
它是Java虚拟机的一部分,负责将%JAVA_HOME%\lib目录或者被 -Xbootclasspath参数指定路径中的,能被虚拟机识别的类加载到虚拟机内存中(如 rt.jar,名字不符合的即时放在lib目录下也不会被加载)
启动类加载器无法被Java程序直接引用,如果用户需要把加载请求委派给启动类加载器,在自定义的类加载器中直接返回null就好了。
扩展类加载器(Extension ClassLoader):
外部类加载器,负责加载%JAVA_HOME%\lib\ext目录,或者被“java.ext.dirs”系统变量指定路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):
它负责加载用户路径,即ClassPath下的所有指定类库,开发者可以直接使用这个类加载器;如果应用程序中没有自定义的类加载器,一般情况下这个就是程序默认的类加载器。
应用程序一般都是由这3中类加载器相互配合完成加载的,如果有必要,可以加入自定义的类加载器,这些类加载器之间的 关系一般如图所示:
这种类加载器之间的层次关系,称为类加载器的双亲委派模型。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,因此所有的加载请求最终都到了顶层的启动类加载器中,只有当父类加载器说自己完成不了这个加载请求,子加载器才会尝试自己加载,自己不行再由下一级类加载器尝试加载。
这样做的好处是:Java类随着它的类加载器一起具备了一种带有优先级的层次关系,比如java.lang.Object类,它放在rt.jar中,无论哪个类加载器要加载这个类,最终都是委派给顶层的启动类加载器加载,因此Object类在程序中的各个类加载器环境中都是同一个类;如果没有这个机制,用户自己编写了一个java.lang.Object类,并放在了程序的ClassPath中,每个类加载器都自行加载,那系统中会出现多个不同的Object类,Java类体系中最基础的行为都无法保障,那就乱了。