类生命周期——七个阶段
类的生命周期包含了七个阶段,加载、验证、准备、解析、初始化、使用、卸载。
类加载的五个阶段
类加载包含五个阶段,加载、验证、准备、解析、初始化。
这五个阶段有两种执行顺序:
1.加载、验证、准备、解析、初始化
2.加载、验证、准备、初始化、解析
解析包含把方法的符号引用替换为直接引用,所以解析在初始化之后运行可以实现动态绑定。
类加载器可以在加载了一部分二进制数据之后运行验证,而验证完一部分数据之后又可以执行准备,准备不需要完全执行完成又可以运行解析或初始化,同样解析和初始化也不一定非要执行完成才能执行下一阶段的初始化或解析。
这些阶段执行顺序是确定的,只要上一阶段给下一阶段准备了足够的数据,就可以执行下一阶段,所以整个执行过程看起来是不断交叉切换运行的。
连接
验证、准备、解析这三个阶段又被称为连接。
类加载五阶段的第一阶段——类加载——含义
类加载即是根据类的全限定名(包括包名)查找并读取类编译之后的二进制数据,解析二进制数据并把其中跟类相关的数据按照JVM要求的格式存储到方法区,接着在堆中实例化java.lang.Class对象,该对象提供了访问方法区中与该类相关的数据的接口。
类加载五阶段的第一阶段——类加载——预加载和运行时加载
类加载可以预先加载类,也可以在运行时需要使用到该类的时候才加载。
预加载如果遇到错误,类加载器并不会立即报错Java.lang.LinkageError,而是会延迟到该类第一次被使用的时候,所以即使类找不到或编译之后的二进制数据有错误,只要在程序中不使用该类,程序还是不会终止运行。
类加载五阶段的第一阶段——类加载——class二进制数据来源的多样性
类加载器可以加载本地文件系统中的.class文件,也可以加载从网络上下载的.class文件,也可以从zip压缩文件、jar归档文件中加载,甚至还可以从数据库中读取class二进制数据。可以看到class二进制数据的来源是多种多样的,比如类加载器可以加载JSP临时编译成的.class文件。
类加载五阶段的第一阶段——类加载——与其他类加载阶段相比,可干预度高
jdk默认提供的类加载器只能加载本地文件系统中的class文件,程序员可以编写自己的类加载器加载其他来源或有特殊要求的class文件。
类加载五阶段的第二阶段——连接三阶段的第一阶段——验证
该阶段是验证class二进制数据包含的数据匹配当前虚拟机的要求,并且没有包含会损害虚拟机的恶意数据。
类加载五阶段的第二阶段——连接三阶段的第一阶段——验证——具体工作
文件格式验证:
.class文件是有规范的,比如,开头为0xCAFEBABE、第5~8个字节表示的是主次版本号等。
文件格式验证主要包括检验class文件的主次版本号是否不高于当前虚拟机,检验常量池中的常量的类型等
元数据验证:语义分析,保证符合Java语言规范,如一个类不能有两个直接父类等。
字节码验证:从数据流、控制流的角度分析语义,比如break之后不能再有其他语句。
符号引用验证:跟下一阶段解析相关。
加载未知第三方class文件的时候验证阶段显得非常重要,但是对于我们自己编译的class文件并非必要,所以可以在运行程序的时候加上-Xverifynone参数来关闭校验,不过一般都没有此必要,因为这耗费的是类加载时间,是在程序启动之前执行的,对用户体验毫无影响。
类加载五阶段的第三阶段——连接三阶段的第二阶段——准备
该阶段在方法区中为静态变量分配内存,并且初始化为对应类型的默认值(零值);而static final变量则被初始化为声明语句赋予的值。
例子:
public static String staticString = "staticString";
staticString在准备阶段会被初始化为null,而不是"staticString"。
一些跟变量相关的提醒:
1.局部变量在使用前就必须赋值,否则编译器报错;
2. static final修饰的变量在声明的时候就必须赋值,否则编译器报错;
3.final变量可以在声明时赋值,也可以在代码块或构造函数中赋值;
基本数据类型的零值(默认值)
byte,short,int:0
float,double:0.0
char等价于short:0
boolean:false。
类加载五阶段的第四阶段——连接三阶段的第三阶段——解析
把.class文件中的符号引用转换为直接引用,包括类、接口、字段、类方法、接口方法、方法的类型、方法句柄和调用点限定符等。
符号引用是用来描述目标的任何字面量,.class文件是平台无关的,所以里面的符号引用也是平台无关的。
直接引用是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
类加载五阶段的第五阶段——初始化
该阶段主要做两件事情,一是给静态变量赋予用户赋予的值,二是执行静态代码块。
虽然初始化是类加载的最后一个阶段,但是加载类的时候并不会执行这最后一个阶段,而是要等到类被主动使用的时候才会执行,解析可以在初始化之后执行,所以解析也是可以不进行的。
在初始化类的时候,如果父类还没有初始化,会先初始化父类。
类加载五阶段的第五阶段——初始化——类初始化时机
-
实例化类(new)
-
访问类或接口的静态变量
-
调用类静态方法
-
反射,比如Class.forName(“com.mysql.jdbc.Driver”))
-
初始化子类(上面集中方法),其父类也会被初始化
-
虚拟机启动的时会先初始化启动类(包含main方法的类)
类加载五阶段的第五阶段——初始化——类什么时候不会被初始化(被动引用的时候)
1、程序中通过子类访问父类的static字段
2、定义某个类的数组
3、访问类的静态常量