【JVM进阶之路】十四:类加载器和类加载机制

在上一章里,我们已经学习了类加载的过程,我们知道在加载阶段需要”通过一个类的全限定名来获取描述该类的二进制字节流“,而来完成这个工作的就是类加载器(Class Loader)。

1、类与类加载器

类加载器只用于实现类的加载动作。

但对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每 一个类加载器,都拥有一个独立的类名称空间。

类加载器和类确定类是否相等

这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

如下演示了不同的类加载器对instanceof关键字运算的结果的影响。

public class ClassLoaderTest { public static void main(String[] args) throws Exception { //自定义一个简单的类加载器 ClassLoader myLoader = 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 (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); //从流中转化类的实例 return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; //使用自己实现的类加载器加载 Object obj = myLoader.loadClass("cn.fighter3.loader.ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); //实例判断 System.out.println(obj instanceof cn.fighter3.loader.ClassLoaderTest); } }

运行结果:

在代码里定义了一个简单的类加载器,使用这个类加载器去加载cn.fighter3.loader.ClassLoaderTest类并创建实例,去做类型检查的时候,发现结果是false。

2、双亲委派模型

从Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器使用C++语言实现,是虚拟机自身的一部分;另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类 java.lang.ClassLoader。

站在Java开发人员的角度来看,类加载器就应当划分得更细致一些。自JDK 1.2以来,Java一直保持着三层类加载器、双亲委派的类加载架构。

双亲委派模型

双亲委派模型如上图:

启动类加载器(Bootstrap Class Loader):负责加载存放在 <JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,能被Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类。

扩展类加载器(Extension Class Loader):负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。

应用程序类加载器(Application Class Loader):负责加载用户类路径 (ClassPath)上所有的类库,如果没有自定义类加载器,一般情况下这个加载器就是程序中默认的类加载器。

用户还可以加入自定义的类加载器器来进行扩展。

双亲委派模型的工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载。

双亲委派机制

为什么要用双亲委派机制呢?

答案是为了保证应用程序的稳定有序。

例如类java.lang.Object,它存放在rt.jar之中,通过双亲委派机制,保证最终都是委派给处于模型最顶端的启动类加载器进行加载,保证Object的一致。反之,都由各个类加载器自行去加载的话,如果用户自己也编写了一个名为java.lang.Object的类,并放在程序的 ClassPath中,那系统中就会出现多个不同的Object类。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzjwdx.html