Java类加载的加载,验证,准备,解析,初始化小结

一个类包括加载,验证,准备,解析,初始化,使用,卸载七个生命周期。

加载阶段:

1.获取Class文件的二进制字节流

2.将二进制字节流对应的静态数据结构转换为方法区中的运行时数据结构(在放入方法区之前先进行文件格式验证)

3.生成一个Class对象作为方法区中的运行时数据结构的入口(该对象放在方法区而不是堆中)

验证阶段:

1.文件格式验证:验证class文件二进制字节流是否符合Class文件规范,只有通过了文件格式验证,才会把二进制字节流放进方法区的运行时数据结构中。

2.元数据验证:验证字节码是否符合java语言规范,如:这个类是否继承了被final修饰的类

3.字节码验证:验证字节码的数据流和控制流是否符合逻辑,及对方法体进行检验分析,保证方法在运行时不会做出危害虚拟机的事

4.符号引用验证:这些验证发生在解析阶段虚拟机将符号引用变成直接引用时。验证如下内容:根据符号引用中的字符描述的全限定名是否能找到对应的类;对应的类是否存在对应的方法和字段,且这些方法和字段是否能被该类访问。验证失败则抛IllegalAccessError

(因为加载一个类时,往往涉及到其他类,因为会持有其他类型的成员或者方法中访问了其他类。而在一个Class文件中,这些对其他类的依赖是通过一个符号引号标识的,因为没有加载时根本不知道这些被引用的类的具体地址。)

准备阶段:

为类变量(静态变量)分配内存,并初始化其值为对应类型的0值。final修饰的类变量除外,final类变量为基本类型时,初始化为实际的值。

解析阶段:

虚拟机实现时可以选择在加载时就解析或者在一个符号引用将要被使用到前才去解析。getfield, getstatic, instanceof, invokexxx, putxxx, new等字节码指令就会使用到对应的符号引用。

1.类或接口的解析:假设这个符号引用为N,如果这个符号引用从未被解析(一般来说,代表这个类未被加载,或这在被加载还未到解析这一阶段),那么就需要去加载这个类,然后就是这个类的加载,验证,准备,解析这些过程。

2.字段解析:假设这个符号引用为N,如果这个字段所属的类的符号引用没被解析过,则先加载这个类,假设这个类为C。先在C本身上找,再到父接口中找(从下到上),再到父类中找(从下到上),找到就返回这个字段的直接引用,找不到则抛NoSuchFieldError最后对这个字段的验证,即符号引用验证。

3.类方法解析:(注意实例方法的解析是在运行时才能进行)待续。。。。。

4.接口方法解析

初始化阶段:

执行<clinit>方法,这个方法在编译时自动收集生成,按源文件中出现的顺序收集静态变量定义和静态代码块,即先出现的先执行。执行本类的<clinit>方法前,先执行父类的<clinit>方法。而执行本类的<clinit>前,不必先执行父接口的<clinit>方法。

初始化阶段有且只有以下五种情况下才会执行:

1.执行putstatic,getstatic,invokestatic,new字节码指令

2.反射调用访问类

3.子类初始化前,先初始化父类

4.虚拟机启动时,用户需要指定一个主类,虚拟机会去初始化这个类

(如果getstatic访问一个常量,那么将不会触发初始化,因为在编译期就将这个值放入常量池)

其中验证,准备,解析属于连接过程。我觉得最应该归为连接过程的阶段时解析阶段,因为这个阶段把在方法区中的各个类使用直接引用连接在一起,可以真正的实现互相访问。

符号引用在常量池中。

猜你喜欢

转载自blog.csdn.net/b1480521874/article/details/91348679