JVM类加载机制(PS:都是热修复惹的祸)

最近Android热修复框架带来了不少的麻烦,准备自己研究一下类加载机制,以后有机会自己写一个,翻了翻资料,准备先从Java虚拟机下手,顺便延伸一下Java深度,反正早晚都要学,哪怕不会也必须要了解的嘛,反正知识这东西,学了也不亏,于是看了看好多前辈的文献,自己用白话总结和理解了一下,毕竟IT最高境界就是给外行人讲的能听明白嘛,开搞。

首先需要了解一下类加载过程:
加载----连接(验证,准备,解析)----初始化

然后要知道每个阶段都在干嘛
加载阶段完成三件事(三步走)
1:根据类的限定名获取该类的二进制字节流
2:把该类的字节流的静态数据结构转化成方法区的运行时的数据结构
3:内存中开辟空间存储这个类的class对象,用来当做后续的数据访问的入口

加载过程和连接过程可以同时进行(异步),当连接过程开始,此时无论是否加载完毕,都算是连接阶段,这两个阶段的开始时间仍然保持着固定的先后顺序。

连接阶段–验证(三个部分)
1 文件格式,核对文件格式是否符合class文件格式要求
2 元数据,对字节码描述的信息去做语义分析
3 字节码,根据数据流和控制流判断程序语义是否是合法,符合逻辑的
4 符号引用,确保解析动作能够正确执行
验证阶段呢,不是必须,它对程序没有影响,如果说需要反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间

连接阶段–准备
这个阶段其实就是分配内存阶段,分配静态变量,但是实例变量是不在这个时候去分配内存的,实例变量是通过初始化的时候去进行操作的
Eg:public static int eg=666;
这个阶段的时候eg的值并不是666而是0,因为这个时候没有执行任何的Java方法,所以这个时候eg的值为0,而666是在为静态变量赋值的时候执行putstatic操作才会被赋值,此动作应该出现在初始化阶段,开辟内存空间并非为赋值。
但是有一种特殊情况就是被final修饰,会在准备阶段初始化为指定的值,这个时候eg的值就会被初始化为666而不是0。

连接阶段–解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。

初始化阶段
之前的开辟内存啊,初步赋值啊,其实都是计算机的主观臆想,属实不算数,这个阶段就是按照我们的意愿根据我们的代码去实际的对变量或者是其他资源去做初始化,中间有一个非常重要的元素就是类构造器(),
其实说白了初始化阶段就是执行类构造器的一个过程,()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。

public class StaticTest
{
    public static void main(String[] args)
    {
        staticFunction();
    }
 
    static StaticTest st = new StaticTest();
 
    static
    {
        System.out.println("1");
    }
 
    {
        System.out.println("2");
    }
 
    StaticTest()
    {
        System.out.println("3");
        System.out.println("a="+a+",b="+b);
    }
 
    public static void staticFunction(){
        System.out.println("4");
    }
 
    int a=110;
    static int b =112;
}

输出结果是:
2
3
a=110,b=0
1
4
看到这个结果,一脸懵逼,23什么鬼?不应该是123吗?那我们从后往前看,这里就是实例变量和类变量的区别所在,类变量是在类被加载的过程中就被开辟的内存空间,可以被使用,而实例变量是当创建了类的实例以后才获得内存空间,此时我们才能对它们进行访问,那么就不难理解了,从上往下看类被实例化的时候开始,正常情况来说静态代码块要比普通的代码块执行优先级要高,但是,结果来看普通代码块却先被执行了,就还是那句类被实例化搞的鬼,当类被实例化以后,这边要搞清楚一点就是被static修饰的归属于类,而没被修饰的代码块部分属于对象,归属于最外层的类,所以当被实例化new之后就紧接着执行他自己的内容所以出现了23的顺序,至于1为什么在4之前就不用多说了,当时不理解苦恼在于2优先于1那里,后来请教了一位老哥才搞清了这里对象和类这个坑,之前没这么写过,误认为是并列平级关系了,所以这里就可以引出来Java中赋值的优先级了:

Java中赋值顺序:

  1. 父类的静态变量赋值
  2. 自身的静态变量赋值
  3. 父类成员变量赋值和父类块赋值
  4. 父类构造函数赋值
  5. 自身成员变量赋值和自身块赋值
  6. 自身构造函数赋值

后面就是调用以及销毁了。

猜你喜欢

转载自blog.csdn.net/qq_39681405/article/details/86598968