Java 类加载、链接、初始化流程细节问题
问:请依据自己的理解重点谈谈你对 Java 类加载、链接、初始化的理解?
答:一个 Java 类从字节码到能在 JVM 中被使用需要经过加载、链接和初始化三个步骤,而从细节上来说又可以分为五个步骤,分别是加载、验证、准备、解析、初始化。
对于我们写代码来说,直接可见的是 Java 类加载(使用 ClassLoader)步骤,而链接和初始化是在使用 Java 类之前的流程。
-
加载是由 ClassLoader 来完成的,ClassLoader 的主要作用是将 Java 字节码转换成 JVM 中的
java.lang.Class
类对象,其次通过双亲委派模式保证了类加载的唯一性和安全性;但是由于双亲委派的存在,所以启动一个类加载过程的类加载器和最终定义这个类的类加载器可能并不是一个(前者称为初始类加载器,后者称为定义类加载器),故一个 Java 类的定义类加载器是该类所导入的其它 Java 类的初始类加载器(譬如 A、B 两个类均未加载,A 中定义了 B 类型的成员,则 A 的定义类加载器会负责启动 B 的加载过程),这个一定要理解。此外 JVM 判断两个java.lang.Class
是否相同不仅要依据类的全路径描述符,还要保证是同一个 ClassLoader 加载。 -
链接是将 Java 类的二进制代码合并到 JVM 运行状态中的过程,链接依赖于成功的加载流程,链接包括验证、准备和解析等几个步骤。
-
验证过程是确保 Java 类二进制结构字节码的正确性,如果验证过程出现错误则会抛出
java.lang.VerifyError
错误。 -
准备过程将创建 Java 类的静态域,同时将这些域的值设为默认值,准备过程不会执行代码。
-
解析过程确保类的继承、组合等关联类、接口能被正确找到,解析的过程可能会导致其它的 Java 类被加载(譬如在一个 Java 类中会包含对其它类或接口的引用或继承,解析就是去确认这些被引用的类能被正确的找到)。不同 JVM 解析策略可能不同,有些是在链接的时候就递归对所有依赖进行解析,有些只会在真正用到时时才进行解析(譬如只在方法中使用到了其他类)。
-
-
初始化是指一个类第一次被使用时 JVM 会进行该类的初始化操作,初始化过程主要是执行类里面的静态代码块和初始化静态域。在一个类被初始化之前,它的直接父类也需要被初始化。但是,一个接口的初始化不会引起其父接口的初始化。在初始化的时候,会按照源代码中从上到下的顺序依次执行静态代码块或初始化静态域。
Java 类和接口的初始化过程只会在特定时机发生,具体如下:
-
创建一个未被使用过的 Java 类实例。
-
调用一个未被使用过的 Java 类静态方法。
-
给一个未被使用过的 Java 类或接口中声明的静态域赋值。
-
访问一个未被使用过的 Java 类或接口中声明的静态域且该域不是常值变量。
此外,一定要搞清楚类加载初始化流程与类实例化流程的区别,这是两个东西,只有在某些情况下(譬如 new 一个初次使用的对象实例等)这两者才会有直接关联,而关联关系也是先有类加载初始化流程,之后才有类实例化流程。
本文参考自 再谈 Java 类加载、链接、初始化流程细节问题