java类加载器双亲委派,类加载器与双亲委派机制

目录

一、类与类加载器

类加载器用于实现类的加载,加载器会把载入内存中的类生成一个java.lang.Class实例对象。对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。也就是说:比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。下面是类加载器对instanceof关键字运算的结果的影响:

package com.me.jvm;

import java.io.IOException;

import java.io.InputStream;

public class ClassLoaderTest {

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {

ClassLoader loader = 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 = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();

System.out.println(obj.getClass());

System.out.println(obj instanceof ClassLoaderTest);

System.out.println(obj.getClass().getClassLoader());

System.out.println(ClassLoaderTest.class.getClassLoader());

/**

* 打印结果:

* class com.me.jvm.ClassLoaderTest

* false

* com.me.jvm.ClassLoaderTest$1@74a14482

* sun.misc.Launcher$AppClassLoader@18b4aac2

*/

}

}

上面的示例显示:在自定义的classLoader去加载了一个名为“com.me.jvm.ClassLoaderTest”的类, 并实例化了这个类的对象。前两行输出结果中, 从第一行可以看到这个对象确实是类com.me.jvm.ClassLoaderTest实例化出来的, 但在第二行的输出中却发现这个对象与类com.me.jvm.ClassLoaderTest做所属类型检查的时候返回了false。 这是因为Java虚拟机中同时存在了两个ClassLoaderTest类, 一个是由虚拟机的应用程序类加载器所加载的(参考第四行结果), 另外一个是由我们自定义的类加载器加载的(参考第三行结果), 虽然它们都来自同一个Class文件, 但在Java虚拟机中仍然是两个互相独立的类。

二、三层类加载器

本节内容将针对JDK 8及之前版本的Java来介绍什么是三层类加载器。正如我们通常认为的那样,绝大多数Java程序都会使用到以下3个系统提供的类加载器来进行加载。分别是:启动类加载器、扩展类加载器、应用程序类加载器。贴出如下代码来查看各层加载器及其父类加载器:

/*示例1:输出加载器的父类加载器*/

Object obj = loader.loadClass("com.me.jvm.ClassLoaderTest").newInstance();//loader为第一节中自定义加载器

//1.1

System.out.println(obj.getClass().getClassLoader());//com.me.jvm.ClassLoaderTest$1@74a14482

//1.2

System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader@18b4aac2

//1.3

System.out.println(ClassLoaderTest.class.getClassLoader().getParent());//sun.misc.Launcher$ExtClassLoader@14ae5a5

//1.4

System.out.println(ClassLoaderTest.class.getClassLoader().getParent().getParent());//null

/*示例2:输出各个类的加载器*/

//2.1

System.out.println(ClassLoaderTest.class.getClassLoader());//sun.misc.Launcher$AppClassLoader

//2.2

System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getClassLoader());//null

//2.3

System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader());//sun.misc.Launcher$ExtClassLoader@14ae5a5

//2.4

System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader().getClass().getClassLoader());//null

从示例一中可以看出,其中三层加载器及自定义加载器是相互补充依赖,并不是继承关系,查看源码的话可以看出加载器ClassLoader类里有一个final修饰的ClassLoader类型的parent属性。加载器的依赖顺序是:自定义加载器的-->应用程序类加载器(AppClassLoader)-->扩展类加载器(ExtClassLoader)-->启动类加载器(null)。从示例二中可以看出,各层加载器加载的类是不一样的。下面是三层加载器的说明 :

·启动类加载器(Bootstrap Class Loader)

这个类加载器负责加载存放在\lib目录, 或者被-Xbootclasspath参数所指定的路径中存放的, 而且是Java虚拟机能够识别的(按照文件名识别, 如rt.jar、 tools.jar, 名字不符合的类库即使放在lib目录中也不会被加载) 类库加载到虚拟机的内存中。 启动类加载器无法被Java程序直接引用, 用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器去处理, 那直接使用null代替即可( null值来代表引导类加载器的约定规则,可参考上面示例2.2或2.4)。

·扩展类加载器(Extension Class Loader) :

这个类加载器是在类sun.misc.Launcher$ExtClassLoader中以Java代码的形式实现的。 它负责加载\lib\ext目录中, 或者被java.ext.dirs系统变量所指定的路径中所有的类库。 根据“扩展类加载器”这个名称, 就可以推断出这是一种Java系统类库的扩展机制, JDK的开发团队允许用户将具有通用性的类库放置在ext目录里以扩展Java SE的功能, 在JDK9之后, 这种扩展机制被模块化带来的天然的扩展能力所取代。 由于扩展类加载器是由Java代码实现的, 开发者可以直接在程序中使用扩展类加载器来加载Class文件(可参考上面示例2.3)。

·应用程序类加载器(Application Class Loader)/系统类加载器 :

这个类加载器由sun.misc.Launcher$AppClassLoader来实现。 由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值, 所以有些场合中也称它为“系统类加载器”。 它负责加载用户类路径(ClassPath) 上所有的类库, 开发者同样可以直接在代码中使用这个类加载器。 如果应用程序中没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。(可参考上面示例2.1)。

三、双亲委派机制

双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求, 它首先不会自己去尝试加载这个类, 而是把这个请求委派给父类加载器去完成, 每一个层次的类加载器都是如此, 因此所有的

加载请求最终都应该传送到最顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类) 时, 子加载器才会尝试自己去完成加载。

使用双亲委派模型来组织类加载器之间的关系, 一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都能够保证是同一个类。 反之, 如果没有使用双亲委派模型, 都由各个类加载器自行去加载的话, 如果用户自己也编写了一个名为java.lang.Object的类, 并放在程序的ClassPath中, 那系统中就会出现多个不同的Object类, Java类型体系中最基础的行为也就无从保证, 应用程序将会变得一片混乱。双亲委派模型对于保证Java程序的稳定运作极为重要, 但它的实现却异常简单,全部集中在java.lang.ClassLoader的loadClass()方法之中, 如代码如下:

protected Class> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// 1、查看是否已经加载过此类

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,说明父类加载器无法加载

}

if (c == null) {

// 假如父类加载此类失败,调用自身findClass方法再进行类加载

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()方法尝试进行加载。

下面是类加载器加载流程图:

4c3adb1fe33cd9ccda9fb880f2d282aa.png

四、总结

本文所用jdk版本为java1.8.0_201,以下是总结:

自定义加载器可以通过重写ClassLoader的loadClass方法来实现。

class都是通过classloader来装载的。

只有当你使用该class的时候才会去装载。

同一个加载器只会装载相同的class一次;同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。

双亲委派机制保证java运行安全稳定。

参考:《深入理解Java虚拟机:JVM高级特性与最佳实践》

本文地址:https://blog.csdn.net/changlina_1989/article/details/112483881

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值