一、加载
java类变成字节码以后,要运行的话需要类加载器,将将类的字节码载入方法区中,内部采用C++的instanceKlass描述java类(底层)。
java无法直接访问instanceKlass,所以中间需要一系列的转换。内部重要的field(域)有:
- _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class(这就是instanceKlass的镜像),作用是把 klass 暴露给 java 使用—桥梁作用
- _super 即父类
- _fields 即成员变量
- _methods 即方法
- _constants 即常量池
- _class_loader 即类加载器
- _vtable 虚方法表 (构造方法入口地址…)
- _itable 接口方法表
注意点:
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
- instanceKlass 这样的【元数据】是存储在方法区(1.8 后的元空间内),加载的同时会在java堆内存中生成_java_mirror的镜像
二、链接
- 验证。 验证类是否符合JVM规范,安全性检查
- 准备。为 static 变量分配空间,设置默认值
总结:static变量分配空间和赋值阶段——准备阶段?初始化阶段?
注意点:static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成。如果static变量是final的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成。如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成。
- 解析。将常量池中的符号引用解析为直接引用
符号引用仅仅只是符号 直接引用后确切知道类在内存中的位置 (字段和方法的内存地址)
三、初始化
该part要想明白的问题: 这个类中的元素到底要不要初始化??
类的初始化时懒惰的(用的时候才初始化)
初始化的定义:执行了<cinit>
方法,也就是执行静态方法,给静态变量赋值
classLoader只会导致类的加载,解析和初始化都不做
四、总结
加载-> 验证-> 准备 ->解析-> 初始化
- 加载 :类加载器查找字节流,创建类的过程
- 验证: 被加载的类要满足jvm的加载条件
- 准备: 为被加载的静态字段分配内存
构造跟类层次相关的数据结构,比如方法表 - 解析 符号引用到实际引用
- 初始化
<cinit>
被final修饰的静态字段,并且数据类型是基本的数据类型或者字符串,会被标记位常量。其余的静态字段和静态代码块会被java编译器一同放进
<clinit>
方法中(类构造器)。 --实例构造器为<init>
在初始化阶段,会为常量值字段赋值,并执行<clinit>
方法。多个线程同时初始化一个类,会通过加锁的方式来确保<clinit>
方法只会被执行一次