跳到主要内容

类加载机制

JVM 的类初始化机制

类加载过程 加载验证准备解析初始化

JVM 加载类

JVM 按需.class 文件中的字节码读入内存,将其放在运行时数据区的方法区(method code)内,最终 在堆区(heap) 中创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。

何时进行类加载

一般来说,只有在第一次 主动调用某个类时才会去进行类加载。如果一个类有父类,会先去加载其父类,然后再加载其自身。

  • 第一次意味着:每个类有且仅有一次初始化的机会。
  • 主动调用
    • 一个类的实例被创建
      • new
      • 反射
      • cloning
      • 反序列化
    • 调用类的 static 方法
    • 使用或对类/接口的 static 属性进行赋值时 (不包括 final 的与在编译期确定的常量表达式)
    • 当调用 API 中的某些反射方式
    • 子类被初始化
    • 被设定为 JVM 启动时的启动类 (具有 main 方法的类)

类加载的生命周期

JVM 将字节码转为运行时对象分为三个阶段, loading、linking、initializing。

Loading

Loading 过程主要由 ClassLoader 完成

  1. 根据类的全限定名称获取其定义的二进制字节流
  • 从本地系统中直接加载
  • 网络
  • zip、jar
  • 专有数据库中提取
  • 将 Java 源文件动态编译
  1. 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  2. 中生成 Class 对象,作为对方法区中这些数据的访问入口。

ClassLoader 双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上, 因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类 时,即无法完成该加载,子加载器才会尝试自己去加载该类。

ClassLoader 继承关系,不同 CL 负责加载不同类

ClassLoader 继承关系,不同 CL 负责加载不同类

Linking

Verification

确保加载的类符合 JVM 规范,并进行安全检查。

Preparation

在这个阶段,JVM 会为类成员变量(不包括实例变量)分配内存空间并且赋予默认初始值,需要注意的是这个阶段 不会执行任何代码,而只是根据变量类型决定初始值。如果不进行默认初始化,分配的空间的值是随机的,有点类型c语言中的 野指针问题。

Resolution

Resolution 阶段主要工作是确认类、接口、属性和方法在类run-time constant pool的位置,并且把这些符号引用 (symbolic references)替换为直接引用(direct references)。

Initialization

这个阶段会去真正执行代码,具体包括:代码块、构造函数、变量显式赋值。

这些代码执行的顺序遵循以下两个原则:

  1. 有static先初始化static,然后是非static的
  2. 显式初始化,构造块初始化,最后调用构造函数进行初始化
class Singleton {

// private static Singleton mInstance = new Singleton();// 位置1 counter1: 1, counter2: 0
public static int counter1;
public static int counter2 = 0;

private static Singleton mInstance = new Singleton();// 位置2 counter1: 1, counter2: 1

private Singleton() {
counter1++;
counter2++;
}

public static Singleton getInstantce() {
return mInstance;
}
}

public class InitDemo {

public static void main(String[] args) {

Singleton singleton = Singleton.getInstantce();
System.out.println("counter1: " + singleton.counter1);
System.out.println("counter2: " + singleton.counter2);
}
}

首次主动调用才会初始化