likes
comments
collection
share

从类加载机制到双亲委派

作者站长头像
站长
· 阅读数 14

什么是类加载

每个编写出的.java文件都存储着需执行的程序逻辑,经过Java编译器编译

会为每个.java文件生成对应的.class字节码文件,.class文件中则记录着Java代码转换之后的虚拟机指令,每个.class文件开头都有特定的标识、魔数版本等信息。

当JVM需要用到某个类时,虚拟机会加载它的.class文件,加载了相关的字节码信息后,会为它创建对应的Class对象,而这个过程就被称为类加载

类加载的过程

类加载过程被分为三个步骤,五个阶段,分别为加载、验证、准备、解析以及初始化。加载、验证、准备、初始化这四个阶段的顺序是确定的。但解析阶段不一定,为了支持Java语言的运行时绑定特性,在某些情况下可以在初始化阶段之后再开始(也称为动态绑定或晚期绑定)

从类加载机制到双亲委派

  1. 加载

    1. 通过完全限定名查找定位.class文件,并获取其二进制字节流数据
    2. 把字节流所代表的静态存储结构转换为运行时数据结构
    3. 中间中为其创建一个Class对象,作为程序访问这些数据的入口
  2. 验证(验证被加载的Class的正确性)

    1. 文件格式验证:验证字节流是否复合Class文件的规范
    2. 元数据验证:对字节码描述的信息进行语义的分析,以保证其描述符合Java语言的规范(语法检查)
    3. 字节码验证:通过数据流和控制流分析,确定程序语义的合法性
    4. 符号引用验证:确保后续的解析工作可以正确执行
  3. 准备:为类中的静态变量分配内存空间(static成员),并完成数值的初始化(例如static int i = 5;这里只会将i初始化为0)

  4. 解析:把类中对常量池内的符号引用转换为直接引用的过程

    1. 符号引用:用一组符号来描述引用的目标,符号引用的字面量形式明确定义在==《Java虚拟机规范》==的Class文件格式中
    2. 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
  5. 初始化:主要是对类的静态变量赋予正确的初始值,也就是在声明静态变量时指定的初始化值以及静态代码块中的赋值

JVM类加载器

从类加载机制到双亲委派

  • 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  • 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现

什么是双亲委派

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类

从类加载机制到双亲委派

双亲委派的作用

  • ①防止加载同一个.class。通过委托去询问上级是否已经加载过该.class,如果加载过了,则不需要重新加载。保证了数据安全。
  • ②保证核心.class不被篡改。通过委托的方式,保证核心.class不被篡改,即使被篡改也不会被加载,即使被加载也不会是同一个class对象,因为不同的加载器加载同一个.class也不是同一个Class对象。这样则保证了Class的执行安全

从类加载机制到双亲委派

双亲委派的核心类——ClassLoader

在Java中,所有的类加载器都间接的继承自ClassLoader类,包括Ext、App类加载器(Bootstrap除外,因为它是C++实现的)

// ClassLoader类 → loadClass()方法
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // 加锁
    synchronized (getClassLoadingLock(name)) {
        // 先尝试通过全限定名从自己的命名空间中查找该Class对象
        Class<?> c = findLoadedClass(name);
        // 如果找到了则不需要加载了,如果==null,开始类加载
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 先将类加载任务委托自己的父类加载器完成
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父类加载器为null,代表当前已经是ext加载器了
                    // 那么则将任务委托给Bootstrap加载器加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 处理异常,抛出异常
            }

            if (c == null) {
                // 如果都没有找到,则通过自定义实现的findClass
                // 去查找并加载
                long t1 = System.nanoTime();
                c = findClass(name);

                // 这是记录类加载相关数据的(比如耗时、类加载数量等)
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        // 是否需要在加载时进行解析,如果是则触发解析操作
        if (resolve) {
            resolveClass(c);
        }
        // 返回加载后生成的Class对象
        return c;
    }
}

// ClassLoader类 → findClass()方法
protected Class<?> findClass(String name) 
            throws ClassNotFoundException {
    // 直接抛出异常(这个方法是留给子类重写的)
    throw new ClassNotFoundException(name);
}

// ClassLoader类 → defineClass()方法
protected final Class<?> defineClass(String name, byte[] b,
        int off, int len) throws ClassFormatError
{
    // 调用了defineClass方法,
    // 将字节数组b的内容转换为一个Java类
    return defineClass(name, b, off, len, null);
}

// ClassLoader类 → resolveClass()方法
protected final void resolveClass(Class<?> c) {
    // 调用本地(navite)方法,解析一个类
    resolveClass0(c);
}

// ClassLoader类 → getParent()方法
@CallerSensitive
public final ClassLoader getParent() {
    // 如果当前类加载器的父类加载器为空,则直接返回null
    if (parent == null)
        return null;
    // 如果不为空则先获取安全管理器
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        // 然后检查权限后返回当前classLoader的父类加载器
        checkClassLoaderPermission(parent,
                Reflection.getCallerClass());
    }
    return parent;
}

ClassLoader类的关键方法,具体作用如下:

loadClass(name,resolve):加载名称为name的类,加载后返回Class对象实例
findClass(name):查找名称为name的类,返回是一个Class对象实例(该方法是留给子类重写覆盖的,在loadClass中,在父类加载器加载失败的情况下会调用该方法完成类加载,这样可以保证自定义的类加载器也符合双亲委托模式)
defineClass(name,b,off,len):将字节流b转换为一个Class对象
resolveClass(c):使用该方法可以对加载完生成的Class对象同时进行解析操作
getParent():获取当前类加载器的父类加载器

如何破坏双亲委派

我们知道类的加载方式默认是双亲委派,如果我们有一个类想要通过自定义的类加载器来加载这个类,而不是通过系统默认的类加载器,说白了就是不走双亲委派那一套,而是走自定义的类加载器

我们知道双亲委派的机制是ClassLoader中的loadClass方法实现的,打破双亲委派,其实就是重写这个方法,来用我们自己的方式来实现即可

  • 如果不想打破双亲委派模型,就重写ClassLoader类中的findClass()方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。
  • 而如果想打破双亲委派模型则需要重写ClassLoader类loadClass()方法(当然其中的坑也不会少)。典型的打破双亲委派模型的框架和中间件有tomcat