超详细的 ClassLoader 详解以及案例分享

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

ClassLoader 被称为 JVM 中的类加载器,具体的作用是将编译的 Class 文件加载到 JVM 运行时数据区中。对于开发者,没搞明白 ClassLoader 的源码以及运行原理,对于日常开…

ClassLoader 被称为 JVM 中的类加载器,具体的作用是将编译的 Class 文件加载到 JVM 运行时数据区中。对于开发者,没搞明白 ClassLoader 的源码以及运行原理,对于日常开发看似没什么太大的影响,但是对于我们理解优秀的开源框架源码以及编写一些底层的平台工具的时候,学会 ClassLoader 定会事半功倍。

将收获如下:

  • 源码剖析 ClassLoader 底层运行机制
  • 介绍常用的开源框架是如何使用 ClassLoader
  • 分享工作中 ClassLoader 的实战经历


简述

class 文件中描述的各种信息,最终都需要加载到虚拟机中才可以运行和使用。在这个过程中需要对数据进行校验、转换解析、初始化,最终可以被虚拟机直接使用,这就是虚拟机的类加载机制。

优点:

  • 提高 Java 程序的灵活性
  • 可以做得到程序启动运行的时候动态拓展加载其它 class 文件

缺点:

  • 类加载会带来一定的性能开销

类加载原理

类的生命周期

enter image description here

  1. 验证、准备、解析我们统称为:连接(Lingking)
  2. 加载、验证、准备、初始化、卸载这 5 个阶段的执行顺序是固定的
  3. 解析在特殊情况下可能在初始化之后执行,因为 Java 支持运行时绑定

类的初始化时机

Java 虚拟机并没有强制约束在什么时候进行类的初始化,这个虚拟机可以自由把握。但是在 Java 虚拟机规范中,有 5 种情况必须立即对类进行初始化(在此之前的几个阶段必须需要完成):

  1. 当执行 new、getstatic、putstatic、invokestatic 4 个指令的时候,如果类没有被初始化,则需要先触发类的初始化。
  2. 使用 Java 发射进行创建类的时候,如果类没有被初始化,则需要先触发类的初始化。
  3. 初始化类的时候父类没有进行初始化,则需要先触发父类的初始化。
  4. 虚拟机启动的时候指定的执行主类,虚拟机会优先初始化这个类。
  5. 如果你还是使用的 JDK 1.7 版本,方法句柄对应的类没有进行初始化,则需要先触发类的初始化。

类的加载过程

下面为大家详细讲解类加载的 5 个阶段具体的操作,具体的细节大家可以阅读周老师的《深入理解 Java 虚拟机:JVM 高级特性与最佳实践》。

加载

“加载”只是“类加载”的一个重要过程,内存中会存在一个 Class 对象,作为方法去中的数据访问入口。类加载器分为系统类加载器、自定义类加载,开发者可以通过自定义的类加载器去控制字节流的获取当时,重写类加载器的 loadClass() 方法。

常见的加载方式:

  • 从压缩包中读取 class 文件,例如:JAR、WAR 等格式。
  • 可以从网络中获取,常见的是 URLClassLoader 可以通过网络获取 class 资源。
  • 程序运行的时候通过动态代理技术可以成功代理类(ProxyGenerateProxyClass)。
  • 其它外部文件,通过 JSP 技术生成对应的 class。
  • 也可以从数据库中读取 class。
验证

验证是 Java 虚拟机对系统的一种保护机制,避免加载了危险的字节流直接导致程序出现问题,验证可以提高 Java 程序的安全性。验证总共分为四个重要的阶段:

  • 文件格式的验证:只有通过了文件格式的校验,字节流才会进入内存的方法区中存储。
  • 元数据的验证:主要对元数据的语义检查,严格按照 Java 语义规范执行。
  • 字节码的验证:主要是验证源码字节的类型、是否合法、是否存在危险等方面进行验证。
  • 符号引用的验证:符号验证例如查找常量池中的符号数据、通过全量限定名查找对应的类等等,主要保障程序能解析正常运行。
准备

准备阶段主要是为加载的变量、常量分配内存空间,这里不包括实例化对象的变量。

超详细的 ClassLoader 详解以及案例分享

解析

将常量池中的符号引用替换为直接引用(内存地址)的过程。

初始化

主要是给静态常量赋值,赋值主要有两种方式:

  • 定义的变量赋值初始值
  • 静态代码块里面的静态常量赋值

类加载器

不同的类加载加载的想通类名的类一定是不相等的,只有同一个类加载器加载的类之间进行对比才会有意义,这点才开发中一定要谨记。

学习类加载器一定需要搞清楚双亲委派机制:

超详细的 ClassLoader 详解以及案例分享

类加载器分为:

  • 启动(Bootstrap)类加载器:主要是加载 JAVA_HOME>\\lib 目录中的文件,例如 rt.jar.C 语言实现,Java 中无法获取到改加载器的引用,可通过 System.getProperty(\"sun.boot.class.path\") 进行查看
  • 扩展(Extension)类加载器:扩展类加载器是由 Sun 的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将 JAVA_HOME /lib/ext 或者由系统变量 -Djava.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器
  • 系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,它负责将用户类路径(java -classpath 或 -Djava.class.path)变量所指的目录,即当前类所在路径及其引用的第三方类库的路径。
  • 自定义类加载器:extends ClassLoader 类,重写 findClass 方法。
类加载器源码剖析

核心代码典型的双亲委派机制,首选判断类是否被加载,如果没有加载委托给父类加载或者委派给启动类加载器加载。

    protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {        synchronized (getClassLoadingLock(name)) {            // First, check if the class has already been loaded            // 判断该类型是否已经被加载              Class<?> c = findLoadedClass(name);            if (c == null) {            //如果没有被加载,就委托给父类加载或者委派给启动类加载器加载                 long t0 = System.nanoTime();                try {                //如果存在父类加载器,就委派给父类加载器加载                     if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                    // 由于启动类加载器无法被 Java 程序直接引用,因此默认用 null 替代                // parent == null 就意味着由启动类加载器尝试加载该类,                  // 即通过调用 native 方法 findBootstrapClass0(String name)加载                         c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                // 父累加器没有加载到,那么加载自定义类加载器,如果到了这层还是不行则 NotFindClass                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    long t1 = System.nanoTime();                    c = findClass(name);                    // this is the defining class loader; record the stats                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);                    sun.misc.PerfCounter.getFindClasses().increment();                }            }            if (resolve) {                resolveClass(c);            }            return c;        }    }

真实案例

建设:低代码平台的过程中,如何对标准业务进行二次开发呢?租户定制化的差异化代码如何集成到标准的业务中?通过类加载器的机制:

  1. 在项目启动的时候在自定义的 ApplicationRunner 中,通过自定义类加载器加载远程类文件。
  2. 系统当前类加载设置为自定义类加载器的父类,当父类无法找到到某个类的时候,会去自定义类加载器查找。
  3. 由于项目 MyBatis XML 是动态加载的,方便租户拓展所以需要设置 MyBatis 的类加载器 Resources.setDefaultClassLoader(definitionLoader);
// mybatis xml 类加器源码public Class<?> classForName(String name, ClassLoader classLoader) throws ClassNotFoundException {    return classForName(name, getClassLoaders(classLoader));}// mybatis 类加载器的顺序,预留了 defaultClassLoader 用于自定义累加器ClassLoader[] getClassLoaders(ClassLoader classLoader) {    return new ClassLoader[]{        classLoader,        defaultClassLoader,        Thread.currentThread().getContextClassLoader(),        getClass().getClassLoader(),        systemClassLoader};  }//  遍历类加载器,挨个查找类文件Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {    for (ClassLoader cl : classLoader) {      if (null != cl) {        try {          Class<?> c = Class.forName(name, true, cl);          if (null != c) {            return c;          }        } catch (ClassNotFoundException e) {          // we\'ll ignore this until all classloaders fail to locate the class        }      }    }    throw new ClassNotFoundException(\"Cannot find class: \" + name);  }

上述简单介绍项目中的类加载器的使用案例,主要涉及加载外部文件,设置系统当前的类加载器为自定义类加载器的父类,如果涉及其它框架(由于很多框架都有自己的一套类加载器),则需要设置自定义类加载器的关系,否则存在 NotFindClass。

小七学习网,助您升职加薪,遇问题可联系:客服微信【1601371900】 备注:来自网站

免责声明: 1、本站信息来自网络,版权争议与本站无关 2、本站所有主题由该帖子作者发表,该帖子作者与本站享有帖子相关版权 3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和本站的同意 4、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责 5、用户所发布的一切软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。 6、您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 7、请支持正版软件、得到更好的正版服务。 8、如有侵权请立即告知本站(邮箱:1099252741@qq.com,备用微信:1099252741),本站将及时予与删除 9、本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章和视频仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。