本笔记为观看寒食君讲解类加载机制视频所做的笔记
视频地址:https://www.bilibili.com/video/BV14U4y1L75q?spm_id_from=333.999.0.0
java文化通过javac编译成字节码文件,然后由JVM加载字节码。
运行时,解释器将字节码解释为一行行的机器码来执行。在此过程中,JIT(即时编译器)会将该部分字节码编译成机器码以获得更高的执行效率。
类加载:把一份被javac编译过的class文本文件通过加载,生成某种形式的Class数据结构进入内存,程序可以调用这个数据结构来构造出object
加载加载是一个读取Class文件,将其转化为某种静态数据结构存储在方法区中,并在堆中生成一个便于用户调用的Class类型对象的过程。
连接类加载之后虽然此时方法区内已经存在了该class的静态结构,堆中也存在了该class类型的对象,但是这并不代表JVM已经完全认可了这个类。如果程序想要使用这个类,就必须进行类的连接。
验证这是连接中的第一步。
验证阶段:对class静态结构进行语法和语义上的分析保证不会产生危害JVM的行为。
验证中包含元数据验证、字节码验证、文件格式验证、符号引用验证(在解析阶段内发生)。
验证的内容也是会不断发展的,除了上面提到的四种验证之外,验证步骤其实已经不断加入了各种机制。
连接中的第二步。
准备阶段:为类中定义的静态变量分配空间。
注意要知道分配空间和赋值是两个步骤。
对于final类型的基本类型静态变量以及字符串常量,在编译阶段就知道其值,那么在准备阶段就完成变量的空间分配以及赋值
对于非final类的静态变量,在准备阶段为变量分配空间且赋值为0,在初始化阶段再进行变量的赋值
对于final类型的引用类型变量,赋值也是在初始化阶段才进行
附:JDK8以前和JDK8及以后内存结构的变化
连接中的第三步。
解析阶段:将符号引用替换为直接引用。
符号引用:当类A被编程成Class之后,假设类A中引用了类B,在编译阶段A不知道B有没有被编译且此时B也没有被加载,那么A此时不知道B的实际地址,所以使用一个字符串来代表B的地址。这个字符串就是符号引用。
直接引用:在运行当中,如果A发生了类加载,在解析阶段发现B还未发生类加载,那么会触发B的类加载,将符号引用替换为B的实际地址,这被称为直接引用。
多态中通过后期绑定来实现多态,而后期绑定就是通过动态解析来实现的。
如果A调用的B是一个具体的实现类,那么就称为静态解析。
如果A是一个抽象类或者接口,且有两个具体的实现类C和D,此时A的具体实现并不明确,无法确定使用哪个类的直接引用来进行替换符号引用。那这时候便会等到运行过程中发生了调用,此时虚拟机调用栈中会得到具体的类型信息,这时候再进行解析,这时候就能使用直接引用来替换符号引用。底层在这里对应了invokevirtual字节码指令。
这个阶段会执行代码中主动的资源初始化动作。这里的资源初始化动作是指成员变量的赋值动作、静态变量的赋值动作、静态代码块的逻辑,而不是指类的构造方法,只有显式地调用new指令,才会调用构造方法进行对象的实例化。
以下是JVM中双亲委派、类加载器的笔记,不过这部分我没做过多的笔记。
视频地址:https://www.bilibili.com/video/BV1X5411K7cw?spm_id_from=333.999.0.0
JVM内置三个重要的ClassLoader,除了启动类加载器之外的加载器都是用Java实现的:
BootstrapClassLoader(启动类加载器):最顶层加载类,由C++实现,负责加载JAVA_HOME下的lib目录下的jar包和类ExtensionClassLoader(扩展类加载器):主要负责加载JAVA_HOME下的lib/ext目录下的jar包和类APPClassLoader(应用程序类加载器):面向用户的加载器,负责加载当前应用classpath下的所有jar包和类
双亲委派:
当一个类加载器收到类加载请求时,会先判断当前类是否已经被加载过。如果当前类没有被加载过,当前类加载器也不会首先自己去加载,而是传递给自己的父亲加载器。只有父亲加载器无法加载,儿子加载器才会尝试自己去加载。因此所有的请求最终都会传送到顶层的启动加载器中。
当父亲加载器是null时,会使用启动类加载器作为父类加载器。
什么时候无法加载呢?根据类的限定名,类加载器没有在自己负责的加载路径中找到该类。
注意这里说的是父亲加载器和儿子加载器,而不是说父加载器和子加载器,因为上面图中这些箭头并非表示继承关系而是一种逻辑关系,使用组合实现(类加载器类中的parent变量表示当前加载器的父亲加载器)。
双亲委派模型的好处(引用自JavaGuide):
双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。



