重点补充:类加载机制
类加载指的是JVM加载字节码(class文件)的过程。
-
这张图是java类的生命周期图,完整一点可以在加载前面加上javac编译这个过程。
-
类加载只包括加载、连接、初始化。注意区分加载和类加载的区别,加载只是类加载的一个过程
-
其中解析部分是灵活的,它可以在初始化之前或之后再进行,实现"后期绑定"。其他顺序不可改变。
逐个了解:
加载:
加载是一个读取class文件,将其转化为某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用的java.lang.Class类型对象的过程。
验证:验证有很多个步骤,分散在各个不同的阶段内
前言:对文件格式的检验其实是发生在加载阶段的,如果通过,才能顺利加载,顺利加载后此时方法区虽然已经存在了该class的静态结构,堆中也存在了该class类型对象,但这并不代表着JVM已经完全认可了这个类,如果程序想要使用这个类,就必须进行连接。
连接的第一步就是对这个类进行进一步的验证,继续对方法区内的class静态结构进行了元数据验证、字节码验证,如果通过,JVM则暂且认为它是安全的。(内容:对class静态结构进行语法和语义上的分析,保证其不会产生危害虚拟机的行为)
还有一道验证,是在解析阶段发生的符号引用验证
一般共4个验证,验证的内容是会不断发展的,从低版本的虚拟机到现在验证步骤其实已经不断加入了各种机制,在未来,虚拟机开发人员可能会引入更多更完善更完美的验证策略。
准备:
为该类型中定义的静态变量赋0值,这里仅仅是静态变量,而不是成员变量。
**拓展:**虚拟机内存规范中定义了方法区这种抽象概念,JDK8之前使用了永久代这种具体的实现方式来实现了方法区,在JDK8以后弃用了“永久代”这种实现方式,采用“元空间”这种直接内存来取代。所以“JDK8及以后采用元空间来替代方法区”这个说法是错误的,因为方法区是抽象概念,而元空间是具体的实现方式,这种说法是完全牛头不对马嘴的。
解析:
将符号引用替换为直接引用
当一个类被编译成class类,假如这个类为A.class,并且A中引用了B,那么在编译阶段,A是不知道B有没有被编译的,而且此时B也一定没有被加载,所以A肯定不知道B的实际地址,那么A如何才能找到B呢?此时在A的class文件中,将使用一个字符串S来表示B的地址,S就被称为“符号引用”。在运行时,如果A发生了类加载,到了解析阶段会发现B还没有被加载,那么就会触发B的类加载,将B加载到虚拟机中,此时A中B的符号引用会被替换成B的实际地址,这被称为“直接引用”,这样之后A就能真正调用到B了。
多态中的“后期绑定”是如何实现的呢?其实就是这里的动态解析:
如果A调用的B是一个具体的实现类,那么就称为“静态解析”,因为解析的目标类很明确。而加入上层java代码使用了多态,这里的B可能是一个抽象类或者是接口,那么它可能有两个具体的实现类C和D,此时B的具体实现并不明确,当然不知道该使用哪个类的直接引用来进行替换,既然不知道,那就等等咯,直到运行过程中发生了调用,此时虚拟机调用栈中将会得到具体的类型信息,就能用明确的直接引用来替换符号引用了,这时候再进行解析,所以就出现了上面的情况,解析又时候会发生在初始化阶段之后,这就是动态解析,用它来实现了上层的后期绑定和多态,底层对应了invokedynamic这条字节码指令。
画个图理解一下:
初始化:此时会判断代码中是否有主动的资源初始化(赋值)动作,如果有那么执行,这里的主动资源初始化动作不是指构造函数,而是class层面的,比如说成员变量的赋值动作,静态变量的赋值动作,以及静态代码块的逻辑,而只有调用new才会调用构造函数进行对象的实例化,这是对象层面的,二者不要混淆。
文章参考视频
https://www.bilibili.com/video/BV14U4y1L75q?spm_id_from=333.337.search-card.all.click