Java JVM
简单来说,类加载就是Java将 .class
文件加载到程序内存中,随时准备被调用,其中所做的一些列动作就是类加载,包括加载、连接、初始化三个步骤,其中连接又分为验证、准备、解析三步。。
加载(Loading)
这是类加载的第一步,其作用是通过类的全限定名来获取定义该类的二进制字节流(编译好的 .class 文件),然后将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构,最后在内存中生成一个代表这个类的 java.lang.Class
对象,作为方法区这个类的各种数据的访问入口。
加载阶段的字节码来源非常广泛,常见的有从本地文件系统中加载 .class
文件,从 JAR
、ZIP
等归档文件中加载类,通过网络加载类,甚至在运行时动态生成类(如使用 Java 动态代理技术)。
连接(Linking)
此阶段又可细分为验证、准备和解析三个步骤:
验证(Verification):目的是确保被加载类的字节码文件符合 Java 虚拟机规范,不会危害虚拟机自身的安全。验证内容涵盖文件格式验证、元数据验证、字节码验证和符号引用验证等多个方面。例如,文件格式验证会检查字节码文件的魔数、版本号等是否符合规范;元数据验证会检查类的继承关系、方法签名等是否合法。
准备(Preparation):为类的静态变量分配内存,并将其初始化为默认值。这里的默认值是指数据类型的零值,例如 int 类型的默认值是 0,boolean 类型的默认值是 false。需要注意的是,对于 final 修饰的静态常量,在准备阶段会直接赋予指定的值。
解析(Resolution):虚拟机将常量池内的符号引用替换为直接引用。符号引用是用一组符号来描述所引用的目标,它与虚拟机的内存布局无关;而直接引用则是直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,和虚拟机的内存布局相关。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等符号引用进行。
初始化(Initialization)
这是类加载的最后一步,在这个阶段,Java 虚拟机才真正开始执行类中定义的 Java 程序代码。初始化阶段是执行类构造器 <clinit>()
方法的过程。<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}
块)中的语句合并产生的。
类初始化的触发时机遵循以下规则:
- 遇到
new
、getstatic
、putstatic
或invokestatic
这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。常见的场景有使用new
关键字实例化对象、读取或设置一个类的静态字段(被final
修饰、已在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法。 - 使用
java.lang.reflect
包的方法对类进行反射调用时,如果类没有进行过初始化,则需要先触发其初始化。 - 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含
main()
方法的那个类),虚拟机会先初始化这个主类。