类加载器及其过程(较上一篇来说,这篇更加底层)

1.Class装载验证流程

  • 加载
    1. 装载类的第一阶段,取得类的二进制流
    2. 转为方法区的数据结构
    3. 在java堆中生成对应的java.lang.Class对象(与loadClass方法有关)
  • 链接
    1. 验证
      1. 目的:保证Class流的格式正确
        1. 文件格式验证
          1. 是否以0xCAFEBABE开头
          2. 版本号是否合理
        2. 元数据验证
          1. 是否有父类
          2. 是否继承了final类
          3. 非抽象类是否实现了所有的抽象方法
        3. 字节码验证(很复杂)
          1. 运行检查
          2. 栈数据类型和操作码数据参数吻合
          3. 跳转指令指定到合理位置
        4. 符号引用验证
          1. 常量池中描述类是否存在
          2. 访问的方法或字段是否存在且有足够的权限
    2. 准备
      1. 分配内存,并为类设置初始值(类的相关信息分配于方法区中 )
        1. Public static int v = 1;在准备阶段,v会被设为默认值0,在初始化的<clinit>中才会被设置为1;
        2. 对于static final类型,在准备阶段就会被附上正确的值,如public static final int v = 1;
    3. 解析
      1. 将符号引用替换为直接引用

一个对car类的run方法的符号引用,它由run方法的全名和相关描述符组成,在解析阶段,java虚拟机会把这个符号引用替换成一个指针,指针指向car类的run方法在方法区的内存位置,指针就是直接引用

 

 

 

  • 初始化
    1. 执行类初始化方法<clinit>
      1. Static变量 赋值语句
      2. Static{}语句
    2. 子类的<clinit>调用前保证父类<clinit>被调用
    3. <clinit>是线程安全的

2.什么是类加载器---ClassLoader

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入的java字节码将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流的获取方式
  • ClassLoader负责装载过程中的加载阶段

3.JDK中ClassLoader默认设计模式

  • ClassLoader的重要方法

  • ClassLoader分类

  • 协同工作

自底向上调用findLoadedClass

自顶向下调用parent.loadClass

4.打破常规模式

  • 父委托机制的问题

顶层的类加载器无法看到底层的类加载器的相关信息

所以如何解决下面的问题呢?

通过Thread.setContextClassLoader()

  • 上下文加载器
  • 是一个角色,一个职责
  • 用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
  • 基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
static private Class<?> getProviderClass(String className, ClassLoader cl,
            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
    {
        try {
            if (cl == null) {
                if (useBSClsLoader) {
                    return Class.forName(className, false, FactoryFinder.class.getClassLoader());
                } else {
                    cl = ss.getContextClassLoader();
                    if (cl == null) {
                        throw new ClassNotFoundException();
                    }
                    else {
                        return Class.forName(className, false, cl);
                    }
                }
            }
            else {
                return Class.forName(className, false, cl);
            }
        }
        catch (ClassNotFoundException e1) {
            if (doFallback) {
                // Use current class loader - should always be bootstrap CL
                return Class.forName(className, false, FactoryFinder.class.getClassLoader());
            }
            else {
                throw e1;
            }
        }
    }

该方法是在rt.jar/javax/xml/parsers/FactoryFinders.class中的方法。展示如何在bootstrap加载器中加载apploader的类

CL是一个上下文加载器,可以突破父委托机制的限制,

forName(String name, boolean initialize, ClassLoader loader) 使用给定的类加载器返回与给定字符串名称的类或接口相关联的类对象,如果loader为空,则使用bootstrap加载器进行加载。如果传入的CL是app加载器,那么就返回使用app加载器进行加载的类对象。突破父委托机制的限制。

父委托机制的破坏

  • 父委托机制是默认模式,但不是必须这么做
  • Tomcat的WebappClassLoader就会先加载自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成网状结构,根据需要自由加载class

破坏父委托机制的例子

重写loadClass方法;先从底层ClassLoader加载

①查找类是否已经被加载,findloadedclass

②找不到,读文件通过IO读入到byte数组,然后通过defineClass定义类,将字节数组转换成类的实例,属于类的加载。

FileNotFound原因是DemoA这个类没有声明任何父类,默认是Object为父类;所以在加载DemoA之前,先加载父类Object,改写loadClass之后,自底向上加载类,即使用OrderClassLoader加载Object,但是在OrderClassLoader中,找不到Object.class文件,而Object.class在rt.jar中,因此抛出异常,请求父类加载器,右上图绿色指出使用apploader加载Object。Object.class是在rt.jar中,应该使用bootstrap进行加载的,这里有点想不通;  之后,就可以顺利加载DemoA了

5.热替换

  • 含义:
    1. 当一个class被替换后,系统无需重启,替换的类立即生效
    2. 例子:

然后再DoopRun运行过程中,将CVersionA.java做如下修改

得到的class文件,覆盖掉原来的class文件,输出发生改变

​​​​​​​

原文链接:加载失败,请重新获取