类的生命周期
在Java中,从类的生命周期而言,一个类包含如下阶段:
- 加载(Loading):查找和导入Class文件
- 链接(Linking):执行校验、准备和解析步骤,其中解析步骤是可以选择的:
- 校验(Verification):检查载入Class文件数据的正确性
- 准备(Preparation):给类的静态变量分配存储空间
- 解析(Resolution):将符号引用转成直接引用
- 初始化(initialization):对类的静态变量、静态代码块执行初始化工作
- 使用(Using):对类执行操作
- 卸载(Unloading):卸载类
加载
装载(Loading)指的是将类的 class 文件读入到内存,并为之创建一个 java.lang.Class 对象,也就是说:
- 当程序中使用任何类时,系统都会为之建立一个 java.lang.Class 对象;
- 一旦一个类被加载到 JVM 中,同一个类就不会被再次载入了;
- 就像一个对象有一个唯一的标识一样,一个载入JVM的类也有一个唯一的标识;
- 在 Java 中,一个类用其全限定类名(包括包名和类名)作为标识;
- 在 JVM 中,一个类用其全限定类名和其类加载器作为其唯一标识;
链接
当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。
验证
验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。Java 是相对 C++ 语言是安全的语言,例如它有 C++ 不具有的数组越界的检查。这本身就是对自身安全的一种保护。
验证阶段是Java非常重要的一个阶段,它会直接的保证应用是否会被恶意入侵的一道重要的防线,越是严谨的验证机制越安全。验证的目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。其主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证:
- 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前的虚拟机加载处理。例如:主,次版本号是否在当前虚拟机处理的范围之内。常量池中是否有不被支持的常量类型。指向常量的中的索引值是否存在不存在的常量或不符合类型的常量
- 元数据验证:对字节码描述的信息进行语义的分析,分析是否符合java的语言语法的规范
- 字节码验证:最重要的验证环节,分析数据流和控制,确定语义是合法的,符合逻辑的。主要的针对元数据验证后对方法体的验证。保证类方法在运行时不会有危害出现
- 符号引用验证:主要是针对符号引用转换为直接引用的时候,是会延伸到第三解析阶段,主要去确定访问类型等涉及到引用的情况,主要是要保证引用一定会被访问到,不会出现类等无法访问的问题
准备
类准备阶段负责为类的静态变量在方法区分配内存,并设置默认初始值;在准备阶段不会分配类的实例变量的内存,实例变量会在对象实例化时随着对象一起分配在Java堆中。比如public static int value=123;
在准备阶段时初始值为0,在初始化阶段才会变为123。
解析
将类的二进制数据中的符号引用替换成直接引用。
- 符号引用:符号引用是以一组符号来描述所引用的目标,符号可以是任何的字面形式的字面量,只要不会出现冲突能够定位到就行。布局和内存无关。
- 直接引用:是指向目标的指针,偏移量或者能够直接定位的句柄。该引用是和内存中的布局有关的,并且一定加载进来的。
初始化
初始化是为类的静态变量赋予正确的初始值,准备阶段和初始化阶段看似有点矛盾,其实是不矛盾的;如果类中有语句:private static int a = 10,它的执行过程是这样的,
- 首先字节码文件被加载到内存后,先进行链接的验证这一步骤;
- 验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,
- 然后到解析,到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。