深入理解Java虚拟机读书笔记(9): 深入理解虚拟机的类加载机制<2>

深入理解Java虚拟机读书笔记(9): 深入理解虚拟机的类加载机制<2>

前面说到通过一个类的全限定名来获取描述此类的二进制字节流从而加载类,这个动作是在虚拟机外部实现的,以便实现自定义,实现这个动作的代码模块称为“类加载器”

一、类与类加载器

对于任意一个类, 都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性, 每一个类加载器, 都拥有一个独立的类名称空间。换句话说,比较两个类是否“相等”, 只有在这两个类是由同一个类加载器加载的前提下才有意义, 否则, 即使这两个类来源于同一个Class文件, 被同一个虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等

二、双亲委派模型

在Java虚拟机中,存在两种类加载器:启动类加载器和其他类加载器。

启动类加载器(Bootstrap ClassLoader)由C++实现,是虚拟机自身的一部分;其他类加载器有Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

从Java开发人员来看,可以更细致划分,一般会用到以下3种系统提供的类加载器。

  • 启动类加载器(Bootstrap ClassLoader):这个类加载器负责将存放在<JAVA_HOME>\lib目录中的, 或者被-Xbootclasspath参数所指定的路径中的, 并且是虚拟机识别的( 仅按照文件名识别, 如rt.jar, 名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。 无法被Java程序直接使用。
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launcher$ExtClassLoader实现, 它负责加载< JAVA_HOME>\lib\ext目录中的, 或者被java.ext.dirs系统变量所指定的路径中的所有类库, 开发者可以直接使用扩展类加载器。
  • 应用程序类加载器(Application ClassLoader):这个类加载器由sun.misc.Launcher$AppClassLoader实现。它负责加载用户类路径( ClassPath) 上所指定的类库, 开发者可以直接使用这个类加载器, 如果应用程序中没有自定义过自己的类加载器, 一般情况下这个就是程序中默认的类加载器。

在应用程序中,一般3种类加载器配合使用。还可以自定义类加载器,它们之间关系如下所示:

在这里插入图片描述

图中的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。双亲委派模型要求除了顶层的启动类加载器外, 其余的类加载器都应当有自己的父类加载器。这里类加载器之间的父子关系一般不会以继承( Inheritance) 的关系来实现, 而是都使用组合( Composition) 关系来复用父加载器的代码。

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

此模型优点:Java类随着它的类加载器一起具备了一种带有优先级的层次关系。 例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都是同一个类。 相反, 如果没有使用双亲委派模型, 由各个类加载器自行去加载的话, 如果用户自己编写了一个称为java.lang.Object的类, 并放在程序的ClassPath中, 那系统中将会出现多个不同的Object类, Java类型体系中最基础的行为也就无法保证, 应用程序也将会变得一片混乱。 如果编写一个与rt.jar中类重名的Java类,可以正常变异但不会被加载执行。

所以,双亲委派模型保证了Java程序的稳定运作。实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass() 方法之中,其代码逻辑是:先检查是否已经被加载过, 若没有加载则调用父加载器的loadClass()方法, 若父加载器为空则默认使用启动类加载器作为父加载器。 如果父类加载失败,抛出ClassNotFoundException异常后, 再调用自己findClass()方法进行加载。源代码如下:

protected synchronized Class<?> loadClass(String name, boolean resolve) throws 			                                                         ClassNotFoundException {
    //首先, 检查请求的类是否已经被加载过了
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null)
                c = parent.loadClass(name, false);
            else {
                c = findBootstrapClassOrNull(name);
            }
        } catch(ClassNotFoundException e) {
            // 如果父类加载器抛出ClassNotFoundException说明父类加载器无法完成加载请求
        }
        if (c == null) 
            //在父类加载器无法加载的时候
			//再调用本身的findClass方法来进行类加载
            c = findClass(name);
    }
    if (resolve)
        resolveClass(c);
    return c;
}

三、破坏双亲委派模型

双亲委派模型并不是一个强制性的约束模型, 而是Java设计者推荐给开发者的类加载器实现方式。双亲委派的具体逻辑实现在loadClass()方法中,JDK 1.2之后已不提倡用户再去覆盖loadClass( ) 方法, 而应当把自己的类加载逻辑写到findClass( ) 方法中, 在loadClass( ) 方法的逻辑里如果父类加载失败, 则会调用自己的findClass( ) 方法来完成加载, 这样就可以保证新写出来的类加载器是符合双亲委派规则的。

双亲委派很好地解决了各个类加载器的基础类的统一问题( 越基础的类由越上层的加载器进行加载) , 基础类之所以称为“基础”, 是因为它们总是作为被用户代码调用的API,假如基础类需要调用回用户的代码,此时就出现了问题。为此,Java设计团队引入了线程上下文加载器。这个类加载器可以通过java.lang.Thread类的setContextClassLoaser( ) 方法进行设置, 如果创建线程时还未设置, 它将会从父线程中继承一个, 如果在应用程序的全局范围内都没有设置过的话, 那这个类加载器默认就是应用程序类加载器。

用户对程序动态性的追求也会破坏双亲委派模型。比如:代码热替换( HotSwap) 、 模块热部署( Hot Deployment) 等。OSGI是模块化的规范,OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。 每一个程序模块( OSGi中称为Bundle) 都有一个自己的类加载器, 当需要更换一个Bundle时, 就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下, 类加载器不再是双亲委派模型中的树状结构, 而是进一步发展为更加复杂的网状结构

OSGi中对类加载器的使用是很值得学习的, 弄懂了OSGi的实现, 就可以算是掌握了类加载器的精髓。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值