前言:
类加载:是将 class 文件中的二进制数据读取到内存之中,然后将该字节流所代表的静态存储结构转换为
方法区
中运行时的数据结构,并且在堆内存中生成一个该类的java.lang.Class
对象,作为访问方法区数据结构的入口
。
类加载过程分析:
加载阶段 | 连接阶段 | 初始化阶段(延迟机制) |
---|---|---|
查找并加载二进制文件,即 class 文件 | 验证:确保类文件的正确性 | 为类的静态变量赋予正确的初始值(代码编写阶段) 延迟机制:类在首次使用的时候才会被初始化 |
准备:为类的静态变量分配内存,并且为其初始化默认值 | ||
解析:符号引用、字段、类方法、接口方法等的解析 |
1.连接阶段的解析过程
……
2.初始化阶段
初始化过程主要是执行 <clinit>()
方法的过程。clinit 是 class initialize 的简写,是在 编译阶段 生成的,包含了所有 类变量的赋值动作 和 静态代码块 的执行代码。执行顺序由执行语句在源文件中的出现顺序(指令重排序)所决定。JVM保证了
【注意】:
public class ClassInit {
static class Parent {
/** 1.对 value 赋值 10 */
static int value = 10;
static {
/** 2.对 value 进行二次赋值 20 */
value = 20;
}
}
static class Child extends Parent {
static int i = value;
}
public static void main(String[] args) {
/** 输出 20,因为父类的<clinit>()方法总是最先执行 */
System.out.println(Child.i);
}
}
类的主动/被动使用:
类在首次使用的时候才会被初始化。使用分为两种:
主动使用
和被动使用
。主动使用 才会导致的类的初始化;被动使用 不会导致类的加载与初始化。
1.主动使用的六种场景:
- new 关键字,直接初始化类对象
- 访问该类中的静态变量时
- 访问该类中的静态方法时
- 对该类进行反射操作时
- 初始化子类,会导致父类的初始化
- 执行 main 函数,其所在的类会发生初始化
2.被动使用的两个场景:
- 构造某个类的数组时
-
实例分析:
public class Singleton {
private static int a = 0;
private static int b;
private static Singleton instance = new Singleton();
private Singleton() {
a ++;
b ++;
}
public static Singleton getInstance() {
return instance;
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
System.out.println("a=" + instance.a);
System.out.println("b=" + instance.b);
}
}
注:对象实例化的时候,会调用类中的构造方法,因此 new Singleton() 语句位置的不同会导致 a、b 结果值的不同。