类加载的时机:
- 创建该类的对象时
- 调用类的类方法时
- 访问类的类变量或为该类的类变量赋值
- 使用反射的方式强制创建某个类的 java.lang.Class对象时
- 初始化某个类的子类
- 使用 Java.exe 命令运行某个主类
一 加载
- 通过一个类的全限定名来获取定义这个类二进制字节流
- 将这个二进制字节流的静态存储结构转换为方法区的运行时存储结构
- 在内存中为这个类创建对应的 java.lang.Class 对象,作为访问方法区中这个类的数据的入口
方法区:1.8 的实现是元空间,在本地内存中,内部采用 C++ 的 instanceKlass 来描述Java类,其中有一个字段 _java_mirror 即Java的类镜像,作用是将Klass的信息暴露给Java使用,堆中的*.class对象和instanceKlass对象互相保存对方的地址,类的实例对象在对象头中保存了 *.class的地址,让对象可以通过 *.class 找到 instanceKlass 的地址,从而可以获取类的各种信息
二 连接
- 验证
- 文件格式验证:验证当前类的二进制字节流是否符号Class文件格式,是否能被当前版本的虚拟机处理
- 元数据验证:验证当前类的二进制字节流所描述的信息是否符合Java语言规范
- 字节码验证:通过数据流和控制流的方式,验证程序的语义是否合法
- 符号引用验证:可以看作是对类自身以外的其他各类信息进行匹配性校验,例如:常量池中的符号引用,通俗的说就是验证当前类是否缺少或者被禁止访问它所依赖的某个类、方法、字段等资源
- 准备
- 为类变量分配空间和赋默认值
- 如果是类常量并且是基本数据类型或字符串,那么赋值会在准备阶段完成,其他类变量或引用类型的类常量赋值都在初始化阶段完成
- 解析
- 将常量池中的符号引用转为直接应用
三 初始化
- 就是执行类构造器 <cinit>v 方法的过程,虚拟机会保证这个方法执行过程中的线程安全
- 这个方法是编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的
注意:编译器收集的顺序是根据语句在源文件中出现的顺序决定的,也就是说静态代码块只能访问定义在它之前的静态变量,定义在它之后的静态变量只能为其赋值,不能访问
类的初始化时机
会触发初始化
- main方法所在的类会被优先初始化
- 首次访问这个类的类变量或类方法时会触发
- 子类初始化,如果父类未完成初始化,会引发父类的初始化
- 子类访问父类的静态变量时,只会触发父类初始化
- Class.forName参数2为true的时候会触发
- new 回导致初始化
不会触发初始化
- Class.forName参数2为false的时候不会触发
- 访问类的类常量(基本类型和字符串时)不会触发
- 类对象.class不会触发
- 创建当前类的数组时不会触发
- 类加载器的 loadClass 方法不会触发