java多线程-类的加载过程
类的加载过程分为三个阶段: 加载,连接,初始化
- 加载: 查找并加载二进制类文件,即 class 文件;
- 连接阶段:
- 验证: 保证加载的 class 文件的正确性, 如魔术因子,文件格式验证、元数据验证、字节码验证和符号引用验证
- 准备: 为类的静态变量分配内存, 并且为其初始化默认值
- 解析: 把类的符号引用转换成直接引用
- 初始化: 为类的静态变量赋值,初始化阶段就是执行类构造器
<clinit>()
方法的过程
解析
符号引用和直接引用
- 符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
- 直接引用(Direct References): 直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同拟机实例上翻译出来的直接引用一般不会相同
初始化
初始化阶段就是执行类构造器()方法的过程,()并不是程序员在Java代码中直接编写的方法,它是Javac编译器的自动生成物
-
<clinit>()
方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问 -
<clinit>()
方法与类的构造函数(即在虚拟机视角中的实例构造器<init>()
方法)不同,它不需要显式地调用父类构造器,Java虚拟机会保证在子类的<clinit>()
方法执行前,父类的<clinit>()
方法已经执行完毕 -
<clinit>()
方法对于类或接口来说并不是必需的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>()
方法 -
Java虚拟机必须保证一个类的
<clinit>()
方法在多线程环境中被正确地加锁同步,如果多个线程同时去初始化一个类,那么只会有其中一个线程去执行这个类的<clinit>()
方法,其他线程都需要阻塞等待,直到活动线程执行完毕<clinit>()
方法, 同一个类加载器下,一个类型只会被初始化一次