Java补完之类加载机制(双亲委派模型)学习笔记

前言

本片文章会讨论另一个Java进阶知识要点类加载机制和双亲委派模型。

概述

JVM的设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到JVM外部来实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块被称为“类加载器”。
PS:若要确定是否相等,首先要看两个类是否是由同一个类加载器加载的。

类加载机制原理(双亲委派模型)

类加载器总体上可以分为两种:
1.启动类加载器,这个类加载器是C++实现的,是JVM的一部分。
2.其他的类加载器,由Java实现,独立于虚拟机,再其外部,并且全部集成自java.lang.ClassLoader.
从开发人员的角度来看,类加载器可以分为三类:
1.启动类加载器,该类加载器负责将存放在JAVA_HOME/lib目录中的,或-Xbootclasspath参数所指定的路径中,并且是JVM识别的类库加载到JVM内存中。它无法被直接饮用,若自定义类加载器时需要委派给启动类加载器,则直接使用null代替即可。
2.扩展类加载器,负责加载JAVA_HOME/lib/ext的目录中的 库,开发者可直接使用扩展类加载器;
3.应用程序类加载器,一般的系统类加载器,负责加载用户类路径上所指定的类库。若没有自定义类加载器,则使用该类加载器加载。
4.自定义类加载器
各个不同级别的类加载器是由一定的层次关系的,这种层次关系被称为双亲委派模型,模型如下图所示。
Java补完之类加载机制(双亲委派模型)学习笔记_第1张图片
双亲委派模型图
双亲委派模型要求除了启动类加载器之外,其余的类加载器都应当有自己的父类加载器,父子关系采用组合而非继承的方式实现,以复用父加载器的代码。
双亲委派模型的实现如下所示:

protected synchronized Class loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null){
        try{
            if(parent != null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //if throws the exception ,the father can not complete the load
        }
        if(c == null){
        //若父类的类加载器无法加载类,则使用子类的加载器
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}

*双亲委派模型的工作工程:
1.该类加载器收到了类加载的请求,
2.将这个请求委派给父类加载器完成
3.若父类加载器反馈无法完成这个加载请求(它 的搜索范围中额米有找到所需的类),子类加载器进行加载。*
也就是说,所有的请求多会传送到顶层的启动类加载器,从顶层开始试图加载类,无法加载再向下传递。

双亲委派模型的意义

Java类随着他的类加载器一起聚类了带有优先级的层次关系。无论哪个类加载器要加载这个类最终都会传递到最顶层的启动类加载器,再向下逐层尝试,这就能保证所有相同的类在各种类加载器环境中都是同一个类,若没有类似的向上传递的过程,由各个类加载器自行去加载的话,系统中会出现多个不同的一个名称的类,各种类的行为无法保证一致,体系崩坏。

类加载机制的扩展

双亲委派模型并不是一个强制性的设计模式,在特殊需求的情况系下,是可以进行扩展的。

JNDI

JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
JNDI的代码是由启动类加载器去加载的,但JNDI的目的是对资源进行集中管理和查找,它需要调用由独立厂商实现并部署的ClassPath下的JNDI接口提供者(SPI),但启动类加载器并不认识这些代码。
因此不得不对双亲委派模型进行“破坏”,Java 设计团队引入了线程上下文类加载器,这个类加载器可以通过java.lang.Thread类的setContextClassLoader()进行设置。若创建线程时还未设置,他将会从父线程中继承一个,若全局都没有,则默认采用应用程序类加载器。通过上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完程类加载的动作,这实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器。

OSGI

OSGi(Open Service Gateway Initiative)技术是Java动态化模块化系统的一系列规范。OSGi一方面指维护OSGi规范的OSGI官方联盟,另一方面指的是该组织维护的基于Java语言的服务(业务)规范。简单来说,OSGi可以认为是Java平台的模块层。
OSGI能够实现热插拔以及动态加载依靠的正是对双亲委派模型的扩展,具体逻辑如下。
但收到类加载请求时,OSGi会按照下面的顺序进行搜索:
1.将以java.*开头的类委派给父类加载器加载;
2.否则,将委派类表名单内的类委派给父类加载器加载;
3.否则,将Import列表中的类威派格Export这个类的Bundle的类加载器加载;
4.否则,查找当前Bundle的ClassPath,使用自己的类加载器加载;
5.否则,查找类是否在自己的Fragment Bundle中,如果在,则委派给Fragment Bundle的类加载器加载;
6.否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载;
7.否则,类查找失败。
上面的查找顺序只有前两条符合双亲委派模型,其余的类查找都是在平级的类加载器中进行的。

小测验

来两道测验试题作为结尾来检验自己有没有真的了解类加载相关的知识把。
1.Java中能不能自己写一个java.lang.Object的类?如果能怎么写,如果不能为什么?
2.请设计一个能够在服务端执行的分析JVM中状态的程序。


提示:
可以尝试自定义类加载器,并破坏双亲委派模型,至于结果和原因请大家自己思考。


参考资料
1.周志明,深入理解Java 虚拟机,机械工业出版社

你可能感兴趣的:(Java补完系列)