博客主页小谢同学的小破站✍️本文由小谢同学的小破站原创首发于 CSDN ☕JavaSE专栏JavaSEJavaEE初阶专栏JavaEE初阶JavaEE进阶专栏JavaEE进阶数据结构专栏数据结构⚙️算法专栏算法MySQL初阶专栏MySQL初阶MySQL进阶专栏MySQL进阶计算机网络专栏计算机网络C语言专栏C语言欢迎点赞 收藏⭐ 留言发现错误欢迎指正✨脚踏实地持续深耕奔赴自己的目标✨----- 分割线 -------类加载全过程 双亲委派1. 类加载过程1.1 加载(Loading)1.2 验证(Verification)1.3 准备(Preparation)1.4 解析(Resolution)1.5 初始化(Initialization)2. 双亲委派模型2.1 类加载器的层级2.2 双亲委派工作流程2.3 双亲委派的好处3. 总结前言这里是小谢同学整理的JVM相关笔记。笔记用于自我复盘巩固有错误欢迎大家指出专栏还有 Java、网络、C 语言等系列笔记欢迎翻阅.同时也希望这篇文章能帮助到你~类加载过程其中前5步是固定的顺序并且也是类加载的过程其中中间的3步我们都属于连接对于类的加载来说分为一下几步:加载-验证-准备-解析-初始化加载(Loading)加载: 加载类的时候,首先会根据全限定类名,去找到对应的.class字节码文件,打开字节码文件,读取二进制数据,把class文件的内容读到内存中;-(双亲委派模型根据对应的全限定类名来进行查找)任意打开一个项目,勾选上对应的信息,我们就能看见对应的.class文件解释:全限定类名: 是指类名加上其完整包路径用于在Java中唯一标识一个类例如 java.lang.String验证(Verification)加载阶段先读取 class 字节流、生成全限定类名,此时JVM会根据读到的二进制.class内容,判断当前是否为符合规格的.class文件依据《Java 虚拟机规范》规定了 class 文件格式。区分两个文档《Java 语言规范》规定 Java 源代码语法《Java 虚拟机规范》规定 class 字节码和虚拟机行为解释:(简单了解即可~)ClassFile{u4 magic;// 魔数 0xCAFEBABE标识合法class文件u2 minor_version;// 次版本号u2 major_version;// 主版本号代表编译的JDK版本u2 constant_pool_count;// 常量池计数器cp_info constant_pool[constant_pool_count-1];// 常量池存储类名、方法名、字面量等常量u2 access_flags;// 访问标识public / final / abstract 等修饰符u2 this_class;// 指向常量池当前类名u2 super_class;// 指向常量池父类名u2 interfaces_count;// 实现接口的数量u2 interfaces[];// 接口索引数组u2 fields_count;// 字段数量field_info fields[];// 字段表描述所有成员变量u2 methods_count;// 方法数量method_info methods[];// 方法表存放方法信息与字节码指令u2 attributes_count;// 属性数量attribute_info attributes[];// 属性表保存源码名、内部类等附加信息}Java的另外一个特性:使用JDK18的可以向前兼容JDK8版本,而JDK8不能解析JDK17生成的.class文件准备(Preparation)准备阶段主要做的事情就是:给类的静态成员分配内存空间放在元数据区。JVM 会把新分配未初始化的内存默认置 0。重点这个阶段静态变量是默认零值还没有赋值为代码写的值。此时读取static 成员拿到的是 0 值。解析(Resolution)将.class文件中的字符串常量放入到内存中,处理符号引用把符号引用替换为直接引用,建立类,方法,字段的内存关联;符号引用.class文件里存的字符串名字只是文本描述不知道内存地址。例如字节码里写调用Person类的show()方法它不会写内存地址只存一串文字Person、show。直接引用内存地址 / 指针能直接定位到目标。解析之后就知道Person.show()在内存的哪个位置直接就能找到。举一个简单的例子:classA{publicvoidtest(){BbnewB();b.say();}}编译成 class 之后字节码里面不会记录B 类、say 方法的内存地址。字节码只保存字符串B、say()—— 这就是符号引用。解析阶段做的两件事:加载class文件里面的字符串常量,放入到内存常量池中;符号引用转化为直接引用JVM 找到 B 类已经加载到内存的位置把字符串名字替换成 B 类、say 方法真实内存地址。之后代码执行的时候直接通过地址找到类、字段、方法。初始化(Initialization)真正对类对象执行初始化操作初始化类的静态成员变量执行静态代码块static{}先加载并初始化父类再初始化子类触发初始化只有类被主动使用时才会走到初始化阶段被动使用只会加载不会初始化。整体简化记忆加载 → 验证 → 准备 → 解析 → 初始化三大阶段划分为加载、链接验证 准备 解析、初始化主动使用:new 这个类的实例AanewA();调用类的静态方法读取 / 修改类的静态变量A.num100;System.out.println(A.num);排除static final编译期常量那是被动反射Class.forName(A);初始化子类时父类会被主动使用classFather{}classSonextendsFather{}SonsnewSon();先初始化父类在初始化子类包含 main 方法的启动类JVM 执行 mainpublicclassDemo{publicstaticvoidmain(String[]args){}}被动使用:通过子类引用父类的静态成员classFather{static{System.out.println(父类初始化);}publicstaticintnum10;}classSonextendsFather{static{System.out.println(子类初始化);}}publicclassDemo{publicstaticvoidmain(String[]args){System.out.println(Son.num);// 输出父类初始化// Son只是被动引用子类静态代码块不执行}}只有父类初始化子类不会初始化子类被动使用创建该类型的数组Test[]arrnewTest[10];这时候只是开辟数组空间没有 new Test 对象Test 类被动使用不会初始化。访问 static final【编译期常量】classTest{publicstaticfinalintA200;// 字面量编译期常量}// mainSystem.out.println(Test.A);// Test被动使用不会执行static代码块注意!!!:如果static final的值运行期才能确定就属于主动使用仅仅声明引用变量不实例化Testtnull;双亲委派模型双亲委派模型是一种类加载机制用于确保Java类加载的安全性和一致性。它的基本思想是每个类加载器在加载类时首先将请求委派给父类加载器只有当父类加载器无法加载该类时才尝试自己加载。(通俗来说就是去找名字叫xxx的文件,然后加载,本质上就是一个查找模型,只不过名字比较高大上)个人更认为这应该叫单亲委派或者父亲委派模型:parent(双亲之一),而不是parents(双亲)类加载器的层级JVM 自带三层类加载器BootstrapClassLoader引导类加载器C 实现加载 Java 核心基础类java.lang包ExtensionClassLoader扩展类加载器加载 jre扩展目录下的类ApplicationClassLoader应用系统类加载器加载我们自己写的项目代码(也就是第三方库)双亲委派工作流程当一个类加载器收到加载类的请求不会自己直接加载优先向上委托交给父加载器去尝试加载一直委托到顶层 Bootstrap 引导类加载器如果父加载器找不到这个类再向下回退由子加载器自己加载。具体为什么要这么设计这就得看当初JVM的设计者是如何想的了~一句话总结向上委托查找向下失败加载双亲委派的好处安全防止 Java 核心类被篡改。比如自己手写java.lang.String向上委托后会加载系统自带的 String不会加载自定义的类。保证类唯一性同一个全限定类名在同一个类加载器环境只会被加载一次避免类重复加载。总结在 JVM 规范中类加载分为 5 个步骤也可以概括为三大阶段加载、链接、初始化。链接阶段包含验证、准备、解析三步。加载通过全限定类名找到.class文件读取字节码数据到内存。验证校验 class 二进制内容确认是合法的 class 文件遵循虚拟机规范。准备在元数据区为 static 静态成员分配内存内存默认置 0此时静态变量为默认零值。解析将 class 中的字符串常量加载进内存把符号引用替换为直接引用。初始化初始化静态成员执行 static 静态代码块先初始化父类再初始化子类。JVM 内置三层类加载器引导类加载器、扩展类加载器、应用类加载器也支持自定义类加载器。双亲委派机制当收到类加载请求加载器会优先向上委托父加载器尝试加载顶层加载器无法找到类时再向下交给子加载器加载。优点安全保护能防止核心 Java 类被恶意替换保证同一个类只会加载一次保证类的唯一性。部分场景会打破双亲委派例如JDBC SPI、Tomcat Web 容器、自定义重写类加载器。