类的生命周期
了解类的加载过程,我们先说一下,类的整个生命周期吧。
类的生命周期有七个阶段:加载阶段,验证阶段,解析阶段,准备阶段,初始化阶段,使用阶段,卸载。
流程如下:
类的加载
首先类的加载和类的生命周期中提到的加载阶段不是同一个概念,不要混淆了。
类的加载的定义:
JVM把描述类的数据从class文件中加载到内存,对数据进行校验,转化解析和初始化,最终形成可以被JVM直接使用的java类型,这个过程就是类的加载。
整个过程的步骤:加载阶段,验证阶段,解析阶段,准备阶段,初始化阶段
流程如下:
类的加载每一个阶段具体做了什么?
加载阶段的处理:
这个阶段是通过类加载器完成。有兴趣可以研究一下
ClassLoader这个类。在这里就不多说了。
1.通过类的全限定名获取定义此类的二进制字节流。
2.将字节流中代表静态存储结构转化为方法区运行时的数据结构。
3.在内存中生产一个代表这个类的class对象,作为方法区访问的入口。
注意:这里只针对非数组类。
小知识:其实加载阶段,开发者可以自定义一个类加载去完成,
即重写
ClassLoader的
loadClass()方法,一般不使用。
验证阶段的处理:
验证是为了确保class文件的字节流包含的信息符合当前虚拟机的要求,不危害到虚拟机本身。因为class文件可以通过很多手段处理得到,不一定是java源码编译生成的。
1.文件格式验证
2.元数据验证
3.字节码验证
4.符号引用验证
准备阶段的处理:
为类的类变量分配内存并设置类变量初始值(被static修饰的变量),这些变量所使用的内存将在方法区中进行分配。
提示:对成员变量,类变量,实例变量概念还不是很理解的。参考我的文章。
注意:这里的初始值“通常情况”下都是是零值。例如:一个类变量为 public static int value = 12;类变量在准备阶段过后,初始值是0.不是12.赋值为12是在初始化阶段。
reference是引用类型。
解析阶段的处理:
JVM将常量池的符号引用代替为直接引用。
符号引用和直接引用的区别:
位置:
符号引用存在于.class文件中,不存在与内存中,与JVM的内存没有 关系,直接引用存在于JVM内存中。
作用:
符号引用:以一组符号来描述目标引用。
直接引用:就是引用自己本身。
初始化阶段的操作:(特别说明,由于初始化阶段设置的知识点比较多,下篇文件会详细说明,在这里大概说一下)
实际就是执行类构造器<clinit>()方法的过程。
<clinit>()方法是由编译器自动收集类的所有变量的赋值动作和静态语句块合并生产的。(也就是说<clinit>()方法编译期间就产生了)
注意:编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它后面的变量,可以赋值但不可访问。静态语句块是不可以对非静态的类变量赋值或访问的。
特别提示:
类被加载时:类的静态模块的代码就执行了。
一下代码编译没问题:
public class Test {
static{
i = 3;
//System.out.println(i);
}
static int i = 1;
}
一下代码编译出错:
public class Test {
static{
i = 3;
System.out.println(i);
}
static int i = 1;
}
一下代码出错:
public class Test {
static int i = 1;
int a = 0;
static{
a = 5;
i = 3;
System.out.println(i);
}
}
<clinit>()的认识:
1.父类的<clilnit>()方法优先于子类的执行。
2.类和接口不一定需要,如果没有静态模块,也没有对变量的赋值,那么编译器是不会生成<clinit>()方法的。