类加载机制
2025-09-24 16:17:08 0 举报
JVM 的类加载机制
作者其他创作
大纲/内容
语法分析
Token 序列Token.INTToken.IDENETFIERToken.EQ
类文件
类信息、静态变量、常量
使用
加载
关于热部署 与 JDBC 对于双亲委派机制的打破
Java 之所以解决程序的安全性、跨平台移植性等问题,最主要的原因是 Java 源代码编译的结果并非是属于本地的机器指令,而是字节码。被编译后的代码,如果想在不同的平台上运行,则无需二次的编译,这就是 Java 一次编译处处运行的思想。
卸载
note0
抽象语法树
将其符号引用转为直接引用
委派给父类加载
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
热部署如何打破?—— 为每个模块使用独立的类加载器
常量池
Application / System ClassLoader
自己加载
通过类全限定名获取定义该类的二进制字节流将字节流的静态存储转为方法区的运行时数据结构在内存当中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
note...
Java 的编译步骤
// 在com.mysql.cj.jdbc.Driver类中static { java.sql.DriverManager.registerDriver(new Driver());}
为打破这种局面的机制,在 java.lang.Thread 类中引用了 contextClassLoader (上下文加载器),其主要通过 contextClassLoader 来完成对所需类的加载。以 MySQL 的驱动为例,JDBC 定义了一个标准的接口 java.sql.Driver ,而各个第三方的数据库厂家去针对于该接口去实现自己的 Driver 驱动。并且在其 /META-INF/services/java.sql.Driver 的目录下生命自己的实现类全限定名。当 DriverManager 在初始化的时候会调用 loaderInitialDrivers() 方法,而在该方法当中的 ServiceLoader.load 方法当中最终会调用到 getContextClassLoader() 方法获取到当前线程的 ClassLoader 对象 contextClassLoader 。而 ServiceLoader 会使用 AppClassLoader 去扫描 /META-INF/services/ 目录下映射的驱动文件包路径,完成声明文件当中的所有 Driver 实现类的加载。
编译
生成字节码
对于 Tomcat 这样的 Web 容器主要是是为每一个 Web 应用(WAR 包),都有属于自己的 WebappClassLoader 。而当需要部署或者重新加载一个 Web 应用时,Tomcat 会首先销毁原有的这个应用独有的 WebappClassLoader 实例,其实这就相当于变相的卸载了这个应用加载的所有类。然后 Tomcat 为这个应用创建一个全新的 WebappClassLoader 实例,这个新的类加载器会去读取更新后的 .class 文件,从而加载到新版本类。关键就在与,通过为每一个应用程序提供了独立的,并且可以随时销毁和重建的类加载器实例,实现了应用级别的隔离和类的重新加载。
父类加载失败
由于 DriverManager 本身是属于 Java 标准库的一部分,在 java.sql.DriverManager 的目录下,位于 rt.jar 当中,交于启动类加载器(Boostarp ClassLoader) 加载。但对于数据库驱动而言,是由第三方的厂商提供的归属于第三方的 jar 包,位于程序的 ClassPath 下所以由应用程序类加载器(AppClassLoader)加载。这样两者之间就产生了矛盾(“父委派子”),根据双亲委派机制的规则,一个类加载器应该先委托父类进行加载。而对于 DriverManager 意味着需要有父加载器(Boostarp) 加载,在这之后需要去加载和调用由子加载器(AppClassLoader)加载的第三方驱动实现。在这种标准的双亲委派模型下是不可能实现的,由于隔离机制父加载器无法\"看见\"或”委托“子加载器。
堆
验证
note3
Last modified 2025-9-24; size 745 bytes MD5 checksum 6f2272dc7b2bfc67ba1c76cddb9ece96 Compiled from \"HelloWorld.java\
加载类的 java.lang.Class 对象
Maven
委派模型
BootStarap ClassLoader
# 案例:尝试篡改 Java.lang.String 核心类 package java.lang; public class String { static { System.out.println(\"我的自定义String类被加载了!\"); }}
完善语法树
如何打破双亲委派?
字节编码0101010011...
JDBC如何打破?—— 线程上下文类加载器
Jar 包
note2
本地方法库
Javap -v -p HelloWorld.Class
准备
类加载器
可以把这个过程想象成一个公司采购流程:CEO(Bootstrap ClassLoader):只能决定购买顶级战略物资(核心JRE库)。部门经理(AppClassLoader):可以决定购买部门所需的办公用品(第三方Jar包)。双亲委派:普通员工(一个类)需要什么东西,必须写申请层层上报,最终由CEO决定买不买。CEO不认识也不关心部门级别的用品。打破委派(JDBC案例):CEO (DriverManager) 需要一种特殊的办公用品(数据库驱动)。但他自己没权限买,也不认识供应商。于是他特派(getContextClassLoader()) 一位部门经理 (AppClassLoader) 去完成这个采购任务。部门经理买回东西后,交给CEO使用。
确保 Class 文件当中的字节流包含的信息完全符合《Java虚拟机规范》的约束,避免因为信息的不安全造成虚拟机的崩溃
语义分析
方法区
为静态变量分配内存空间以及默认值初始化,例如,static int value = 123; 在此阶段会被初始化为0,而不是123。
编译成 .Class 文件
词法分析
初始化
主动引用&被动引用
方法表集合
运行时数据区
Extension ClassLoader
note1
# Java 源代码int valueA = 100;int valueB = valueA;
public class Test { public static void main(String[] args) { String s = new String(); // 我们想使用我们自己定义的String类 System.out.println(s.getClass()); }}
1.是否是 16进制 cafebaby 开头2.版本号校验3.元数据检查4.字节码验证5.符合引用检查
将编译后的代理逻辑文本的引用转为了指针指向物理地址引用
执行引擎
// 在DriverManager的loadInitialDrivers方法中ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);// ... 迭代ServiceLoader来加载和实例化驱动
虚拟机栈
MagicNumber
本地库接口
当调用 Javac 命令进行编译的时候,会执行 com.sum.tools.javac.main.Main 类下的 main 方法,内部调用 compile() 方法执行编译。
从原理和结论上看,双亲委派确实是很好的保证了原有核心程序的安全可靠性,但对于一些特殊的场景下,这种双亲委派模型反而限制了程序的灵活性。
执行类构造器方法的过程
假如这个类还没有被加载和连接,则程序先加载并链接该类假如该类的直接父类还没有被初始化,则先初始化其直接父类假如类中有初始化语句,则系统依次执行这些初始化语句
本地方法栈
程序计数器
JVM 启动,执行Test类当中的 main 方法。当遇到 new String() 时,应用程序类加载器 (AppClassLoader) 接受到了加载 java.lang.String 类的请求。AppClassLoader不会自己尝试加载,而是立即将这个请求委派给它的父加载器——扩展类加载器(ExtClassLoader)。ExtClassLoader同样不会加载,继续委派给它的父加载器——启动类加载器(Bootstrap ClassLoader)。Bootstrap ClassLoader在JAVA_HOME/lib/rt.jar中找到了java.lang.String类,成功加载了JDK自带的String类,并将其返回。由于这个加载请求在到达Bootstrap ClassLoader时就已经被处理了,AppClassLoader和ExtClassLoader都没有机会去加载我们自己在ClassPath下伪造的那个java.lang.String类。
假设用户自定义了一个核心类,并且自定义的包路径与 核心类的包路径一致,即便是用户引用了自定义的核心类对象(由于包路径同源,其实但从代码层面是无法看出来),但都是 JVM 而言其在运行的过程当中还是会加载原有核心类,而不是自定义的类。
解析
链接(Link)
双亲委派机制的原则就是保证程序从一开始运行到结束,都保持其原有的唯一性,但在某些场景下比如热部署,其要求在不重启 JVM 的情况下更新应用程序的某个类,并且立即生效。这很明显双亲委派机制下对于已经加载的类,是无法被替换的直到JVM 结束,另一方面对于已经成功加载的类,会被缓存起来。而之后对于已经缓存的类,只有命中都会返回缓存当中的旧版本,而新版本的类文件根本没有机会被再次的加载。处于以上的原因,这就使得在修改更新某个程序的类(例如修复一个 BUG)之后,并无法立即生效。
My ClassLoader
0 条评论
下一页
为你推荐
查看更多