美文网首页Androidjava学习芝华
我对类加载器 ClassLoader 的理解

我对类加载器 ClassLoader 的理解

作者: ab029ac3022b | 来源:发表于2018-11-26 14:11 被阅读862次

    引言

    当刚学 c/c++ 程序时,第一次在控制台下运行程序,需要将所有程序文件进行编译、链接,然后再运行。这很容易理解,毕竟我在一段程序中引用了另一个文件中定义的函数,当然要把这些文件连接到一起。但是在运行 java 程序时却不需要这么做了,顶多在运行时加一个 -classpath 参数。这背后的原因就是 jvm 和 java 中的 ClassLoader 机制。

    什么是 ClassLoader

    当 c/c++ 运行时,是将代码编译成机器码,链接成可运行文件扔给 cpu 运行,但是 java 不是这么做的,它的运行是在 jvm 中的,而关键是 java 并不是把所有要运行的代码打包一起扔给 jvm ,jvm 内部刚开始只加载了你 java XXX 命令中指定的那个初始类 (有 mian 函数的那个类),在这个类运行过程中需要其他类时,再使用类加载器将需要的类(.class 文件)加载到 jvm 中。到这里便引出了三问题

    • 类是经过哪些过程加载到 jvm 中的 ?
    • 什么时候是"需要其他类的时候" ?
    • 所谓的"类加载器"具体到代码到底是个啥玩意 ?

    类是经过哪些过程加载到 jvm 中的 ?

    一 、根据类名在指定的路径下找到对应的 .class 文件
    二、 解析 class 文件,在方法区中(不了解方法区可以先看看 jvm 内存模型) 分配空间,并创建 Class 对象:Class 对象就是 Class 类型的对象 (类可以创建对象,而类本身就是一个 Class 类型的对象 ,它是保存着类的 类型信息 的对象。)深入请看https://blog.csdn.net/bingduanlbd/article/details/8424243
    三、 对 Class 对象进行一系列初始化操作 (静态变量赋值,执行 static 代码块.....)

    (在其他博客和书中会将这个加载过程分为五个步骤,但是要介绍这些步骤即会扯到文件格式验证,引用解析等一堆东西,这篇文章旨在简单介绍,不予赘述。)

    什么时候是"需要其他类的时候" ?

    一 、 使用 new 关键字实例化对象的时候、读取或设置一个静态字段 ( final 修饰除外),调用一个静态方法时。
    二 、 使用反射加载一个类时
    三、 加载一个类时它的父类还没有加载
    四、 启动时指定的类,就是引言中所说的那种情况

    (还有一种是与 java 对动态类型的支持相关的,并不常见,可以参考《深入理解 java 虚拟机》)

    所谓的"类加载器"具体到代码到底是个啥玩意 ?

    java 已经定义好的类加载器有:启动(Bootstrap)类加载器扩展(Extension)类加载器系统(App)类加载器安全(Secure)类加载器URL类加载器 它们的继承关系是这样的:


    其中 ClassLoader 是一个抽象类,定义了类加载器的基本工作机制,SecureClassLoader 在此基础上支持了一些权限控制相关操作,URLClassLoader 则又加入了一些方便定义加载路径的接口,其余的三个类加载器 BootstarpClassLoader,AppClassLoader,ExtClassLoader 都会由 jvm 创建出实际的对象,完成实际的功能 (下文有介绍)。

    分析 ClassLoader 的源码可以大致了解类加载器的工作原理,这里不贴源码分析了,介绍一下它的主要方法和调用关系,源码可以自己看。

    ClassLoader 中与加载类相关的方法:

    方法 说明
    loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
    findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
    findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
    defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
    resolveClass(Class<?> c) 链接指定的 Java 类。

    调用关系:


    整个的过程就是 :
    一、先在缓存中找,已经加载过的就不加载了,直接返回 。

    二、没有在缓存中找到,调用"父类"的 loadClass() 方法,委托"父类"加载,这就是所谓的双亲委派模型。加引号是因为不是真正的父类,而是它在一个的字段 parent 中保存的父类加载器,在 jvm 创建加载器的对象时指定,与上面继承关系图中表示的不同,委托关系图如下:


    如果你想自定义一个类加载器用来加载你特殊文件路径下的类,或是要对加密字节码文件进行解码,可以继承 ClassLoader ,SecureClassLoader,URLClassLoader 中的一个,覆写 findClass() 方法,大多数情况下,继承自 URLClassLoader 最为简单。

    三、"父类"加载失败,调用具体加载器自己定义的 findClass(String name) 函数比如 ExtClass-Loader 的 findClass() 执行的就是去 <JAVA_HOME>/lib/ext 这个路径下查找 class 文件,而 BootstarpClassLoader 则是去 <JAVA_HOME>/lib 下去查找,它们俩个都负责加载一些 java 程序运行需要的基础组件,然后是AppClassLoader,它负责到你的项目路径中加载你写的项目。如果你要自己写一个类加载器,应该继承自 URLClassloader ,然后重写它的 findClass() 方法,如果在构造方法中不指定"父类"加载器,则它的默认"父类"就是 AppClassLoader 。

    (在看源码时你可能会发现 ExtClassLoader 甚至连 findClass()这个函数都没有,那是因为它继承自 URLClassLoader, 直接用路径完成父类构造,然后调用父类的 findClass() 就可以了,按上文理解也没有什么问题)

    四、找到了 class 文件把字节码读进来,还要用 defineClass() 将字节码转变为真正的 Class 对象,也就是将字节码载入 jvm。

    五、对生成的 Class 对象进行最后的链接操作,链接类或接口包括验证和准备类或接口、它的直接父类、它的直接父接口、它的元素类型(如果是一个数组类型)及其它必要的动作。

    双亲委派模型的优点

    一、主要是为了安全性,避免用户自己编写的类动态替换 Java的一些核心类。
    二、同时也避免了类的重复加载,因为 JVM中区分不同类,不仅仅是根据类名,相同的 class文件被不同的 ClassLoader加载就是不同的两个类。

    破坏双亲委派模型和线程上下文类加载器 待续.....

    相关文章

      网友评论

        本文标题:我对类加载器 ClassLoader 的理解

        本文链接:https://www.haomeiwen.com/subject/njalqqtx.html