类加载阶段
1. 加载
- 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
- _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
- _super 即父类
- _fields 即成员变量
- _methods 即方法
- _constants 即常量池
- _class_loader 即类加载器
- _vtable 虚方法表
- _itable 接口方法表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
2. 链接
-
第一阶段:验证
验证类是否符合 JVM规范,安全性检查
-
第二阶段:准备阶段,为static变量分配空间,设置默认值
-
static变量在JDK7之前存储于instanceKlass末尾,在JDK7开始,存储于_java_mirror末尾(instanceKlass位于方法区,_java_mirror位于堆中。1.8之前方法区位于永久代,之后将方法区置于元空间)
-
static变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
-
如果static变量是final的基本类型或者字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
-
如果static变量是final的,但属于引用类型,那么赋值也会在初始化阶段完成
示例:
static int a; static int b = 10; static final int c = 20; static final String d = "HelloWorld"; static final LocalDate e = LocalDate.now();
编译之后的结果:
static int a; descriptor: I flags: ACC_STATIC static int b; descriptor: I flags: ACC_STATIC static final int c; descriptor: I flags: ACC_STATIC, ACC_FINAL ConstantValue: int 20 static final java.lang.String d; descriptor: Ljava/lang/String; flags: ACC_STATIC, ACC_FINAL ConstantValue: String HelloWorld static final java.time.LocalDate e; descriptor: Ljava/time/LocalDate; flags: ACC_STATIC, ACC_FINAL
能够看到字母c d在编译完成后就已经有值了。
-
第三阶段: 解析
将常量池中的符号引用解析为直接引用
public class Load2 { public static void main(String[] args) throws ClassNotFoundException, IOException { ClassLoader classloader = Load2.class.getClassLoader(); // loadClass 方法不会导致类的解析和初始化 Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C"); // new C(); System.in.read(); } } class C { D d = new D(); } class D { }
这里因为加载的时候,使用的懒加载,所以此时加载类C不会加载类D,在常量池中,涉及到类D的信息,会告诉类D是一个未经解析的类。但是使用new对象时,就不一样了。
3. 初始化
初始化调用()v方法,虚拟机会保证这个类的构造方法线程安全。
发生时机
概括得说,类初始化是懒惰的。
- main方法所在的类,总会被首先初始化
- 首次访问这个类的静态变量或静态方法时
- 子类初始化,如果父类还没初始化,会引发
- 子类访问父类的静态变量,只会触发父类的初始化
- Class.forName
- new会导致初始化
不会导致类初始化的情况
- 访问类的static final静态常量(基本类型和字符串)不会触发初始化
- 类对象.class不会触发初始化
- 创建该类的数组不会触发初始化
- 使用Class.forName方法,第二个参数设为false的情况(默认为true)
示例:
public class Load3 { static { System.out.println("main init"); } public static void main(String[] args) throws ClassNotFoundException { // 1. 静态常量(基本类型和字符串)不会触发初始化 System.out.println(B.b); // 2. 类对象.class 不会触发初始化 System.out.println(B.class); // 3. 创建该类的数组不会触发初始化 System.out.println(new B[0]); // 4. 不会初始化类 B,但会加载 B、A ClassLoader cl = Thread.currentThread().getContextClassLoader(); cl.loadClass("cn.itcast.jvm.t3.B"); // 5. 不会初始化类 B,但会加载 B、A ClassLoader c2 = Thread.currentThread().getContextClassLoader(); Class.forName("cn.itcast.jvm.t3.B", false, c2); // 1. 首次访问这个类的静态变量或静态方法时 System.out.println(A.a); // 2. 子类初始化,如果父类还没初始化,会引发 System.out.println(B.c); // 3. 子类访问父类静态变量,只触发父类初始化 System.out.println(B.a); // 4. 会初始化类 B,并先初始化类 A Class.forName("cn.itcast.jvm.t3.B"); } } class A { static int a = 0; static { System.out.println("a init"); } } class B extends A { final static double b = 5.0; static boolean c = false; static { System.out.println("b init"); } }
-