概述
Java 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
Java天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
类加载时机
类加载分为三个步骤:加载,连接,初始化;
如下图 , 是一个类从加载到使用及卸载的全部生命周期,图片来自参考资料;
加载,验证,准备,初始化和卸载这五个阶段的顺序是确定的,类型的加载必须按照这种顺序按部就班地开始。
严格规定有且只有6种情况必须对类进行"初始化"
1:遇到new,getstatic,putstaic 或 invokestatic 这四条字节码指令时,如果类型没有进初始化,则需要先触发其初始化阶段。能够生成这四条指令的典型Java代码场景有
- 是有new关键字实例化对象的时候
- 读取或设置一个类型的静态字段
- 调用一个类型的静态方法的时候
2:使用java.lang.reflect 包的方法对类型进行反射调用的时候,如果类型没有进行过初始化,则需要先触发其初始化
3:当初始化类的时候,如果发现其父类没有进行过初始化,则需要先触发其父类的初始化
4:当虚拟机启动时,用户需要指定一个执行的主类(包含main方法的类),虚拟机会先初始化这个类
5:当使用JDK7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic,REF_putStatic,REF_invokeStatic,REF_newInvokeSpecial,
四种类型的方法句柄,并且这个方法句柄对应的类型没有进行过初始化,则需要先触发其初始化
6: 接口中定义了JDK8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化
类加载过程
加载
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据接口
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证
确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束,保证这些信息被当作代码运行后不会危害虚拟机自身安全
例如:
- 字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
- 这个类的是否继承了不允许被继承的类
准备
- 正式为类中定义的变量(静态变量,被static修饰的变量)分配内存并设置类变量初始值的阶段
public static int value = 123; 先设置初始值为0,在设置值为23
解析
Java 虚拟机将常量池内的符号引用替换为直接引用的过程
Object object = new Object(); // 将对象赋值给 object,此时对象还未初始化
初始化
类的初始化是类加载过程的最后一个阶段,直到初始化阶段,java虚拟机才真正开始执行类中编写的Java程序代码,将主导权移交给应用程序
初始化阶段:执行类构造器< clinit >() 方法的过程。< clinit >() 是java代码编译的产物