目录
1、类加载过程概述
2、加载
3、连接
3.1 验证
3.1.1 文件格式验证
3.1.2 元数据验证
3.1.3 字节码验证
3.1.4 符号引用验证
3.2 准备
3.3 解析
4、初始化
想必大家一般在网上看类加载过程的资料时,通常资料只会将类加载过程概括成以下几点:
不可否认类加载的过程确实如上,但是大家知道里面的具体细节是怎么样的嘛?别急,本文接下来将会对以上过程一一进行详细的说明。
以上几个阶段是按顺序依次“开始”的。注意!这里说的是“按顺序开始”,而不是“依次、串行运行”。比如:可能加载的过程中,连接阶段已经开始,二者交叉运行。
“加载”阶段是整个“类加载”过程中的第一阶段(大家注意区分二者),在类加载阶段,Java虚拟机主要完成以下三件事情:
大家可以发现,加载过程中“获取”类的二进制字节流,我并没有强调是从哪里获取,《Java虚拟机规范》中也并没有要求从哪获取,如何获取。正是因为没有要求,这也为Java后来加载类玩出各式各样的花样提供了基础。比如获取类二进制字节流的途径可以随便举以下例子:
连接我们通常分为三个过程:验证、准备、解析。
验证是加载阶段的第一个阶段,通常是保证class文件的字节流中包含的信息是有效的,符合《Java虚拟机规范》的全部约束要求,确保这些信息运行后不会产生危害。
其中,验证阶段主要是对以下四个方面进行验证:①文件格式验证。②元数据验证。③字节码验证。④符号引用验证。
该阶段主要是为了验证字节流是否符合class文件格式的规范(因为文件格式是可以手动更改的,比如我们随便写了个txt文本,然后将文件后缀更改成 “.class” 结尾,如果不加以验证的话,肯定会出大问题的),并且能被当前版本的虚拟机运行。比如对以下几点进行验证:
该阶段作用是保证输入的字节流能够正确被解析并且加载于方法区内。该阶段的验证是基于二进制字节流进行的,只有通过该阶段,二进制字节流才能被加载进方法区,因此后面的几个阶段是基于方法区的数据结构进行的。
这一阶段主要是对字节码描述的信息进行语义分析,主要是保证字节码描述的信息符合《Java语言规范》的要求,比如:
这一阶段主要是通过数据流分析和控制流分析,确定程序语义是合法的。该阶段对方法体进行校验分析,保证该方法运行时候不会做出危害虚拟机的不安全行为。验证点例如:
这一阶段的校验行为发生在“符号引用”转为“直接引用”的时候,这个转化实际上发生在“连接”的第三阶段“符号解析阶段”。符号引用验证实际上是对类本身之外的各种类信息进行验证。验证点例如:
准备阶段实际上是对类中定义的“类变量”,即静态变量(被static关键字修饰的变量)分配内存并设置初始值的阶段。
对于内存的分配,从概念上来说,JDK7以前是在方法区中给static变量分配内存,JDK8以后是在堆中。
准备阶段的主要工作上述已经描述了,这里还需要特别强调:一般情况下,这一阶段仅为类变量分配内存和初始化默认值。因此!实例变量(没被static关键字修饰的变量)是不会在该阶段分配内存和初始化默认值的!
这里分配内存大家肯定都能理解,但是大家可能会有个疑惑:“什么叫初始化默认值?默认值是什么?”
默认值实际上就是我们在定义一个变量时,不设置值时,Java对于这种没设置值的变量赋予了一个“初始值”。这里我们给出基本类型的默认值(也可以叫初始化值、零值):
boolean false
char '/uoooo'(null)
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
1、int类型定义的数组 默认是0
2、String类型定义的数组 默认值是null
3、char类型定义的数组 默认值是0对应的字符
4、double类型定义的数组 默认值是0.0
5、float类型定义的数组 默认值是0.0
比如我们以下代码,对于cug这个int类型的类变量,在这个阶段过后的值就是0,而不是2023:
public static int cug = 2023;
当然了,大家再看看我们上面说的那句话提到了“一般情况”,言外之意就是还有“特殊情况”,那么特殊情况是什么呢?那就是被“final”关键字修饰的类变量。由于被“final”关键字修饰的变量都是常量,是不可变的。对于这种类变量会在字段属性表中存在“ConstantValue”属性,那么此时在准备阶段便不是按零值来初始化,而是设置成“ConstantValue”属性所指定的值,例如以下代码在这个阶段后的值是2023,即我们指定的值,而不是零值。
public static final int cug = 2023;
解析阶段实际上就是将符号引用转为直接引用的过程。
这个阶段一般来说比较复杂,本文不做过多描述,大家有兴趣的可以去看看周志明老师的《深入理解Java虚拟机》,里面讲的非常细节!!!强力推荐!!!
初始化阶段是类加载过程的最后一个阶段。在前面的准备过程中,我们已经为“一般情况”的类变量(即被static关键字修饰且不被final关键字修饰的变量)进行了内存的分配和设置零值。那么这个阶段大家肯定就能猜到要做什么了:“根据我们程序写好的代码进行其他变量的内存分配、设置我们指定的值”。
初始化阶段实际上就是执行类构造器
方法主要是编译器自动收集类中所有类变量的赋值操作和静态代码块(static{ })中的语句合并而成,合并后的先后顺序是按我们程序中编写的先后顺序来决定。
对于