Skip to content

虚拟机类加载机制

litter-fish edited this page Dec 25, 2019 · 3 revisions

类的生命周期

b94c3ff52774f682d48aa38d35ebb7ff.jpeg

加载、验证、准备、初始化和卸载这5个步骤顺序是确定的,类的加载过程不必须按照这个步骤进行。 为了支持Java语言的运行时绑定,解析阶段可以在初始化阶段后再开始

类的主动加载

  • 使用new关键字实例化对象
  • 读取或设置一个类的静态字段,对于一个类的静态字段,只有直接定义这个字段的类才会被初始化
  • 调用一个类的静态方法
  • 对类进行反射调用时,如果类没有进行初始化则需先进行初始化
  • 当初始化一个类时,如果发现父类还未被初始化,则进行父类的初始化
  • 虚拟机初始化启动类

类加载过程

加载

  • 通过一个类的全限定名来获取定义在此类的二进制字节流
  • 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

验证

确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

  1. 文件格式验证

验证字节流是否符合Class文件格式规范,并且能够被虚拟机处理

  • 是否以魔数0xCAFFBABE开头
  • 主次版本号是在当前虚拟机处理范围之内
  • 常量池中是否存在不被支持的常量类型 ...... 目的: 保证输入的字节流能够正确地解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求
  1. 元数据验证

对字节码描述信息进行语义分析

  • 这个类是否有父类
  • 这个类是否继承了不允许被继承的类(被final修饰的类)
  • 如果这个类不是抽象类,是否实现了父类或接口中要求实现的所有方法 ...... 目的: 对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息
  1. 字节码验证

通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的

  • 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作
  • 保证跳转指令不会跳转到方法体以外的字节码指令上 ......
  1. 符号引用验证

发生在虚拟机将字符引用转为直接引用的时候

  • 符号引用中通过字符串描述的全限定名是否能够找到对应的类
  • 符号引用中的类、字段、方法访问性是否可被当前类访问

目的: 确保解析动作能够正常执行。

准备

正式为类变量分配内存并设置类变量初始值 仅对类变量(static修饰的变量)、而不是实例变量进行初始化 将变量设置为对应类型的零值

解析

将常量池内的符号引用替换为直接引用

  1. 类和接口的解析

  2. 字段解析

  3. 类方法解析

  4. 接口方法解析

初始化

执行类构造器方法

类加载器

比较两个类是否“相等”,只有在这两个是由同一个类加载器加载的前提下才有意义 种类:

  • 启动类加载器 负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数指定的路径中的并且是被虚拟机识别的类库加载到虚拟机内存中

  • 扩展类加载器 由sun.mics.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被Java.ext.dirs系统变量所指定的路径中的所有类库。

  • 应用程序类加载器 由sun.mics.Launcher$AppClassLoader实现,负责加载用户类路径上所指定的类库

双亲委派模型

b59e2c3e4956b2f600d22255566938b2.jpeg

工作过程: 如果一个类加载器收到类加载的请求,检查是否已经加载了该类,未加载则进行加载操作,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,以此向上进行查找,最终所有的请求加载都会送到顶的启动类加载器中,只有当父类反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载

类加载过程

19451205c979e640f9e1fba7af23169d.jpeg

源码解析

类层次结构 5d433abca1a710a95a56c3f9cf260fe0.png

JVM 加载Class文件到内存的方式

  • 隐式加载:继承或引用某个类时,由JVM负责加载
  • 显示加载:调用loadClass(),Class.forName,ClassLoader的findClass方法等,显式加载中也可能包含隐式加载

重要方法

  • findClass: URLClassLoader中实现,根据URLClassPath指定的路径加载class文件,
  • defineClass:可以将字节流解析成Class对象,该Class对象并未进行resolve
  • resolveClass:对Class对象进行Link,载入引用类(超类,接口字段,方法签名,方法中的本地变量)
  • loadClass:根据类名加载一个类,返回Class对象,调用前面3个方法实现

加载class文件的过程

显示加载过程

调用this.getClass().getClassLoader().loadClass("className");时的过程

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先判断该类型是否已经被加载
        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
            }

            if (c == null) {
                // 如果父类加载器不能完成加载请求时,再调用自身的findClass方法进行类加载,若加载成功,findClass方法返回的是defineClass方法的返回值
                // 注意,若自身也加载不了,会产生ClassNotFoundException异常并向上抛出
                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;
    }
}

加载字节码到内存

protected Class<?> findClass(final String name)
    throws ClassNotFoundException
{
    final Class<?> result;
    try {
        result = AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class<?>>() {
                public Class<?> run() throws ClassNotFoundException {
                    // 将完整的类名转换成文件路径格式
                    String path = name.replace('.', '/').concat(".class");
                    // 在指定的URLPath中获取对应的class文件资源
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            // 获取成功将资源传入,最终获取未解析的Class对象
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        return null;
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        // 因为要在特权操作中抛出ClassNotFoundException,使用了PrivilegedExceptionAction回调
        // 它会将异常包装,这里要解除包装
        throw (ClassNotFoundException) pae.getException();
    }
    if (result == null) {
        throw new ClassNotFoundException(name);
    }
    return result;
}

获取Class对象

private Class<?> defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        // Check if package already loaded.
        Manifest man = res.getManifest();
        definePackageInternal(pkgname, man, url);
    }
    // 使用nio,获取字节缓冲区
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        // Use (direct) ByteBuffer:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        // 获取不到缓冲区,直接InputStream获取字节数组
        byte[] b = res.getBytes();
        // must read certificates AFTER reading bytes.
        // CodeSigner 代码签名
        CodeSigner[] signers = res.getCodeSigners();
        // CodeSource 代码源
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}

验证

  • 字节码验证;
  • 类准备:准备类中每个字段、方法和实现接口所需的数据结构;
// src.zip!/java/lang/ClassLoader.java
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
        ProtectionDomain protectionDomain)
    throws ClassFormatError {

    // 首先检查类名;阻止加载“java.”开头包内的类(应有BootStrapLoader加载);
    // 确保同一个包内的Class拥有相同的证书
    protectionDomain = preDefineClass(name, protectionDomain);
    // 根据CodeSource获取一个URL的字符串表示
    String source = defineClassSourceLocation(protectionDomain);
    // 字节码验证;类准备(准备字段,方法,实现接口所必需的数据结构)
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    // 通过证书为该类设置签名
    postDefineClass(c, protectionDomain);
    return c;
}

预准备处理

private ProtectionDomain preDefineClass(String name,
                                        ProtectionDomain pd)
{
    // 检查名称
    if (!checkName(name))
        throw new NoClassDefFoundError("IllegalName: " + name);

    // 阻止加载“java.”开头包内的类(应有BootStrapLoader加载)
    if ((name != null) && name.startsWith("java.")) {
        throw new SecurityException
            ("Prohibited package name: " +
             name.substring(0, name.lastIndexOf('.')));
    }
    if (pd == null) {
        pd = defaultDomain;
    }

    // 确保同一个包内的Class拥有相同的证书
    if (name != null) checkCerts(name, pd.getCodeSource());

    return pd;
}

解析:resolveClass方法

// 解析:resolveClass方法,直接通过一个native方法实现
// 进行LINK,装入引用类,如超类,接口,字段,方法中使用的本地变量。
protected final void resolveClass(Class<?> c) {
    resolveClass0(c);
}

private native void resolveClass0(Class<?> c);
Clone this wiki locally