JVM类初始化过程
目录
类加载机制
类加载机制主要有三步: 加载、连接、初始化。
加载
- 把编译好的class文件加载进内存 (1.7及之前为方法区,1.8取消方法区,用元数据区替代) 中,并在堆中创建对应的Class对象。
连接
验证
- 验证加载的类信息是否符合JVM规范,确保安全
准备
- 为静态变量分配内存并设置默认的初始值
解析
- 将类的二进制数据中的符号引用替换成直接引用
初始化
-
静态变量赋值 -> 静态代码块 -> 成员变量 -> 构造代码块 -> 构造方法
-
其中,成员变量、构造代码块和构造方法在实例化时即创建对象时才执行
使用
卸载
- 以上五个阶段(加载 连接 初始化 使用 卸载)为一个类的生命周期,在此并不做深入讨论
示例
Father.java
public class Father {
private static String staticMember;
private static A a = new A();
static {
System.out.println("执行Father的static块前 staticMember: " + staticMember);
System.out.println("执行Father的static块前 a: " + a);
System.out.println("执行Father的static块");
staticMember = "静态成员变量";
System.out.println("执行Father的static块后 staticMember: " + staticMember);
System.out.println("执行Father的static块后 a: " + a);
}
private String member;
private A a1 = new A();
public Father() {
System.out.println("执行Father的无参构造方法前 member: " + member);
System.out.println("执行Father的无参构造方法前 a1: " + a1);
System.out.println("执行Father的无参构造方法");
member = "成员变量";
System.out.println("执行Father的无参构造方法后 member: " + member);
System.out.println("执行Father的无参构造方法后 a1: " + a1);
}
public Father(String member) {
this.member = member;
System.out.println("执行Father的有参构造方法");
}
}
Son.java
public class Son extends Father {
private A a = new A();
private static B b = new B();
static {
System.out.println("执行Son的static块 b: " + b);
}
public Son(){
System.out.println("执行Son的无参构造方法 a : " + a);
}
public static void main(String[] args) {
System.out.println(1);
Son son;
son = new Son();
System.out.println("son : " + son);
}
}
A.java
public class A {
static {
System.out.println("执行A的static块");
}
public A() {
System.out.println("执行A的无参构造方法");
}
}
B.java
public class B {
static {
System.out.println("执行B的static块");
}
public B() {
System.out.println("执行B的无参构造方法");
}
}
文字流程
因为程序入口在Son类 需进行加载、连接、初始化
- 因为Son继承Father 所以加载Son之前先加载Father字节码文件 -> 加载Father.class
- 加载Father后给静态成员变量分配内存空间并设置默认初始值 -> String staticMember = null; A a = null;
- 初始化Father
- 为静态成员变量 a 赋值 -> a = new A()
- 因为方法区中没有找到A的字节码文件 需要加载A的字节码文件 -> 加载A.class
- 初始化A
- 执行A的静态代码块
- 实例化A
- 执行A的无参构造方法
- 执行Father的静态代码块
- 为静态成员变量 a 赋值 -> a = new A()
- 加载Son字节码文件 ->加载Son.class
- 加载Son后给静态成员变量分配内存空间并设置默认初始值 -> B b = null;
- 初始化Son
- 为静态成员变量 b 赋值 -> b = new B()
2. 因为方法区中没有找到B的字节码文件 需要加载B的字节码文件 -> 加载B.class
2. 初始化B- 执行B的静态代码块
- 实例化B
- 执行B的无参构造方法
- 执行Son的静态代码块
- 为静态成员变量 b 赋值 -> b = new B()
- 因为Son继承Father 所以实例化Son前先实例化Father -> Son(){ super(); … }
- 实例化Father
- 为成员变量分配内存空间并设置默认初始值 -> String member = null; A a1 = null;
- 为成员变量 a1 赋值 -> a1 = new A()
- 因为A.class已经加载进方法区 所以无需再次加载 -> 寻找并得到A.class
- 因为A的静态代码块已经随着A的加载执行了 所以不会再次执行 -> 跳过静态代码块
- 实例化A
- 执行A的无参构造方法
- 执行Father的无参构造方法
- 实例化Son
- 为成员变量分配内存空间并设置默认初始值 -> A a =null;
- 为成员变量 a 赋值 -> a = new A()
- 因为A.class已经加载进方法区 所以无需再次加载 -> 寻找并得到A.class
- 因为A的静态代码块已经随着A的加载执行了 所以不会再次执行 -> 跳过静态代码块
- 实例化A
- 执行A的无参构造方法
- 执行Son的无参构造方法
结果展示
执行结果
相关内存图
ps:此图中方法入栈出栈未标出,若标出具体的连线会显得图太乱,便以此图贴出。若文章中有出错的地方,请多多指教,谢谢