双亲委派模型

一、类加载器

1. 作用

实现 通过一个类的全限定名来获取描述该类的二进制字节流 动作,即类的加载动作。

在虚拟机中,每个类加载器都有一个独立的类名称空间,故只有在 两个类的类的全限定名相同,且加载该类的加载器相同 的情况下,才判定相等(包括 equals()isAssignableFrom()isInstance() 方法及 instanceOf 关键字的判断结果)。

2. 分类

启动类加载器 (Bootstrap Class Loader)

负责加载存放在 <JAVA_HOME>\lib 目录,或者被 -Xbootclasspath 参数所指定的路径中存放的,且文件名能被识别的类库(如 rt.jartools.jar,文件名不符合目录正确也不会被加载)加载到 JVM 内存中。此加载器无法被 Java 程序直接使用,自定义类加载器若需要委派加载请求给此加载器加载,直接使用 null 代替即可。

扩展类加载器(Extension Class Loader)

负责加载 <JAVA_HOME>\lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定路径中的所有类库。此类库中存放具有通用性的扩展类库,且允许用户自行添加,即扩展机制。在类 sun.misc.Launcher$ExtClassLoader 中以 Java 代码形式实现,故用户可直接在程序中使用此类加载器加载 Class 文件。JDK 9 中,此扩展类加载器被平台类加载器替代。

平台类加载器(Platform Class Loader)

由于模块化系统中,整个 JDK 都基于模块化构建,故Java 类库为满足模块化需求,未保留 <JAVA_HOME>\lib\ext 目录,扩展类加载器也被替换为平台类加载器。

应用程序类加载器(Application Class Loader)

负责加载用户类路径(ClassPath)上所有类库,开发者可直接使用此类加载器。由于此加载器在 ClassLoader 类中是方法getSystemClassLoader() 的返回值,故又称系统类加载器。若用户未自定义加载器,一般情况下为默认加载器。

自定义类加载器

可通过重写 ClassLoader 类的 findClass() 方法实现自定义类加载器,以完成某些功能。

二、双亲委派模型

1. 描述

如果一个类加载器收到了类加载的请求,它不会加载自己尝试加载此类,而是委派请求给父类加载器进行加载。

2. 意义

共享

使 Java 类随着它的类加载器一起具备了一种 带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父类加载器已经加载了该类时,子 ClassLoader 就没有必要再加载一次。

隔离

隔离功能,保证核心类库的纯净和安全,防止恶意加载,避免了 Java 的核心 API 被篡改。

保证唯一

若不采用双亲委派机制,同一个类有可能被多个类加载器加载,这样该类会被识别为两个不同的类。

双亲委派机制在很大程度上防止内存中出现多个相同的字节码文件,加载类的时候默认会使用当前类的 ClassLoader 进行加载,只有当你使用该 class 的时候才会去装载,一个加载器只会装载同一个 class 一次。

3. 源码

源码比较简单,全部集中在 java.lang.ClassLoaderloadClass() 方法中。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求类是否被加载过了
            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,说明父类加载器无法完成加载请求
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 在父类加载器无法加载时,再调用本身的 findClass 方法进行类加载
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 记录统级信息
                    ...
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

先检查请求加载的类型是否已加载过,若没有则调用父加载器的 loadClass() 方法;若父类加载器为空则默认使用启动类作为父加载器。若父类加载器加载失败抛出 ClassNotFoundException 异常,才调用自己的 findClass() 方法尝试进行加载。

4. 双亲委派模型图

图1. JDK1.8 双亲委派模型

如图,即为 JDK 1.8 及以前的双亲委派模型图,除顶层类加载器外,其余类加载器都必须有自己的父类加载器。类加载器的父子关系一般 不以继承关系 实现,而是 组合关系 复用父加载器代码。

图2. JDK 9 双亲委派模型

JDK 9 中因模块化的加入而重构了目录结构,也顺带将扩展类加载器替换为平台类加载器。虽然总体上仍保持三层类加载器和双亲委派架构,但委派关系发生变动。

当平台及应用程序类加载器收到类加载请求,在委派给父类加载器前,要先 判断是否归属于某一个系统模块,如果找到归属关系,则优先委派给对应模块的类加载器。由此,也可以算是类加载器的 第四次被破坏

三、双亲委派模型的三次破坏

双亲委派模型仅仅是 Java 设计者推荐开发者们的类加载器实现方式,并不是强制约束的模型。截至目前,大多数 Java 圈的类加载器都遵循此模型,但仍出现过三次较大规模破坏。

1. 兼容 JDK 1.2 前的程序

产生原因

由于双亲委派模型是 JDK 1.2 才被引入,但 Java 第一个版本即存在抽象类 java.lang.ClassLoader,开发者已编写好自定义类加载器,若进行 JDK 升级,则会导致 loadClass() 被覆盖,而正确做法应为重写 findClass()

解决方案

为了兼容已存在的用户自定义类,Java 设计者们只能在 JDK 1.2 后添加一个新的 protected 方法 findClass(),并引导用户尽可能在类加载逻辑中重写此方法,而不是在 loadClass() 中编写代码。按照 loadClass() 方法逻辑,父类加载失败,会调用自己的 findClass() 方法完成加载,保证新代码也可以符号双亲委派规则。

2. 自身的缺陷

产生原因

双亲委派模型很好地解决了各个类加载器协作时基础类型一致性问题,即越基础的类由越上层的类加载器加载。但基础类型也会存在调用回用户代码的场景。

场景

典型的例子便是 JNDI 服务,此服务已是 Java 标准服务。它的代码由启动类加载器完成加载(即在 rt.jar 中),属于 Java 中很基础的类型。但 JNDI 存在的目的就是对资源进行查找和集中管理,故需要调用其他厂商实现并部署在应用程序 ClassPath 下的 JNDI 服务提供者接口(SPI),但启动类不可能识别且加载这些代码。

解决方案

为解决此问题,Java 设计团队引入了线程上下文类加载器。此加载器可通过 java.lang.Thread 类的 setContextClassLoader() 方法进行设置,如果创建线程时未设置,它将从父类继承一个,如果应用程序全局范围内都未设置,则这个类加载器默认为应用程序类加载器。故以此即可加载所需的 SPI 服务代码。此方式为一种父类加载器请求子类加载器完成类加载的行为。

3. 实现程序动态性

产生原因

程序动态性即代码热替换、模块热部署等功能。

场景

OSGi 是实现热部署的常用规范,其实现热部署的关键是它自定义的类加载器机制的实现,每一个程序模块(在 OSGi 中称为 Bundle)都有一个类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码热替换。

实现方式

OSGi 环境下,类加载器不再是双亲委派模型推荐的树状结构,而是更加复杂的网状结构,它的委派关系仅少部分遵守双亲委派模型,其余部分会在平级类加载器中查找。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容