3个阶段:加载、连接、初始化。
其中连接这步可以细分为验证、准备、解析3个步骤。
加载:
按全类名找到class文件或者jar包里的class文件,二进制流读取,然后转换流静态数据结构为方法区的运行时数据结构,把类定义加载到方法区。
然后在堆里边生成一个Class对象作为方法区类定义的入口。(btw:一个对象的对象头里边除了markword之外还有个类型指针、它指向的是方法区的类定义)
注:数组类型不由类加载器加载,由Java虚拟机直接创建。
连接:
其实连接的一部分工作是跟加载是一起重叠进行的,
验证的话主要是验证class文件字节流的格式规范、语义是否符合Java规范、确保后面的解析能够顺利进行。
准备的话主要是为类static变量分配内存和初始值(各种类型自己的默认值,比如int就是0,特殊的如果是final类型的话,那么这时就直接赋最后的值了,比如staic final int i = 111; 这个时候就是111了而不是0),都是在方法区分配的。
解析步骤主要是将常量池中的符号引用替换为直接引用,例如把对某一个方法的调用中的方法名替换为方法存放在方法区内存中的偏移量等等。(程序执行的底层原理就是这样,类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符这些是没法直接识别并执行的,需要它们的内存地址也就是偏移量)
初始化:
类加载的最后一步,执行字节码中的Java类构造器<clinit>()
,如下几种情形会执行类初始化:
- 执行
new
,getstatic
,putstatic
,invokestatic
这几种指令的时候,对应new一个对象,读取/赋值一个静态类变量,执行一个静态方法。 - 反射调用类的时候,如果类没有初始化,则先初始化。
- 子类初始化之前先触发父类的初始化。
- JVM启动时,先初始化包含main方法的主类。
- 当使用 JDK1.7 的动态动态语言时,如果一个 MethodHandle 实例的最后解析结构为 REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄没有初始化,则需要先触发类初始化。
参考
https://mp.weixin.qq.com/s/eHqFONXXNc-LD4ugaKM6UA
https://www.cnblogs.com/jiading/articles/12559998.html