双亲委派模型

目录

1.1类加载器

1.2分类

1.3双亲委派模型

1.3.1工作过程

1.3.2作用

1.3.3实现

1.3.4打破双亲委派模型


1.1类加载器

类加载器就是加载所有类的工具,它加载的类在内存中只会存在一份,即生成的Class字节码对象;不可重复加载.

虚拟机设计者将类加载阶段中"通过类的全限定名获取的二进制字节流"的动作放到虚拟机外部去实现,以便让程序自己决定如何去获取所需要的类;实现这个动作的代码模块被称为"类加载器".

1.2分类

1.引导类加载器(Bootstrap ClassLoader)

虚拟机本身的一部分,底层由C++实现,主要负责加载核心类,即%JAVA_HOME%/jre/lib目录中,或者-Xbootclasspath参数指定的路径中,且文件名被虚拟机识别的类库;等同于所有类加载器的父类.

引导类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可.

2.扩展类加载器(Extension ClassLoader)

由Java实现,独立于虚拟机.主要负责加载%JAVA_HOME%/lib/ext目录中或者被java.ext.dirs系统变量所指定的路径中的类库.

开发者可以直接使用扩展类加载器.

3.系统类加载器(Application ClassLoader)

由Java实现,独立于虚拟机.主要负责加载用户类路径Classpath上指定的类库.

开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器.

1.3双亲委派模型

双亲委派模型,其实是一种类加载器的层次模型.该模型要求除了顶层的引导类加载器外,其它的类加载器都要有自己的父类加载器.

类加载器层次关系图:

1.3.1工作过程

当一个类加载器收到类加载的请求时,不会自己直接加载这个类,而是将请求委托给父类加载器去完成;因此所有的加载请求均会传送到顶层的引导类加载器中;父加载器会在其搜索范围内寻找需要的类,同时判别此类是否有被加载过.只有当父加载器无法完成加载请求,子加载器才会自己尝试去加载.

1.3.2作用

  • 使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类(核心类)得到统一.

    例如:java.lang.Object存放在%JAVA_HOME%/jre/lib目录下的rt.jar类库中,我们在用户类路径Classpath自定义一个java.lang.Object,程序编译可以通过;但由于双亲委派模型的存在,rt.jar中的Object会比Classpath中的Object优先级更高,在发出类加载请求时,此请求会通过层层委派到达最顶层的引导类加载器,就会使得直接加载rt.jar中的Object.

  • 避免了多份同样字节码的加载,保证类加载的唯一性.

    内存是宝贵的,没必要保存相同的两份 Class 对象,例如 java.lang.Object ,实际我们需要一个 Object 的 Class 对象,并且只需要一份,如果不使用委托机制,而是自己加载自己的,就等同于使用自定义的Object来动态的替代java核心API中的Object,在占用内存的同时这其中也会存在极大的安全隐患,而使用委托机制可以有效的避免这个问题.

问:为什么要使用双亲委派模型?(如何保证类加载的唯一性?)

1.3.3实现

实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法中,如下所示:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // 首先判断该类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    //如果父加载器不为空就调用父加载器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 抛出类未找到异常
                }
​
                if (c == null) {
                    // 如果还未找到就调用findClass方法寻找想要的类
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }

Java中提供的默认类加载器,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:我要加载网络上的类,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑.在这样的情况下,默认的类加载器就不能满足我们的需求了,所以需要定义自己的类加载器.

1.3.4打破双亲委派模型

1.自定义类加载器(Custom ClassLoader)

通过继承java.lang.ClassLoader并且重写findClass()方法来定义自己的类加载器.

当ClassLoader.loadClass()方法搜索不到需要的类时,通过调用重写的findClass()方法来寻找类.

2.线程上下文类加载器(Context ClassLoader)

线程上下文类加载器是从 JDK 1.2 开始引入的.类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器.如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器.Java 应用运行的初始线程的上下文类加载器是系统类加载器.在线程中运行的代码可以通过此类加载器来加载类和资源.

实际开发中,我们需要需要使用第三方的类实现Java提供的服务者接口(Service Provider Interface,SPI),比如:JDBC.

SPI接口是Java核心类库的一部分,是由引导类加载器来加载的,而实现这些接口的第三方类(作为 Java 应用所依赖的 jar 包被包含进类路径Classpath)是由系统类加载器来加载;由于双亲委派模型的存在,引导类加载器无法委派系统类加载器来加载类,而线程上下文类加载器破坏了“双亲委派模型”,可以在执行线程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值