类加载器与ClassFileFormate
JVM / JRE / JDK
JVM
- Java 虚拟机(Java Virtual Machine)它是运行所有 Java 程序的虚拟计算机
- 所有的 Java 程序会首先被编译为
.class
的类文件,这种类文件可以在虚拟机上执行- Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点
- 所以 Java 程序运行时比较高效,而且,由于字节码并不专对一种特定的机器
- 因此,Java 程序无须重新编译便可在多种不同的计算机上运行
JRE
- JRE 是 Java runtime environment(Java运行环境)
- 光有 JVM 还不能让
class
文件执行,因为在解释class
的时候 JVM 需要调用解释所需要的类库lib
- jre 目录里面有两个文件夹
bin
和lib
,在这里可以认为 bin 里的就是jvm
,lib 中则是 jvm 工作所需要的类库,而 jvm 和 lib 和起来就称为 jre
JDK
- JDK 是 java development kit(java 开发工具包)
- 程序员做 Java 开发时所需要的一些工具
javac.exe
/jar.exe
,Java 基础的类库 / API
关系
- JDK 包含 JRE,而 JRE 包含 JVM
Java 编译过程
.class
→ 机器码
- 在这一步 jvm 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢
- 有些方法和代码块是经常需要被调用的,也就是所谓的热点代码
- 后面引进了
JIT
编译器,JIT 属于运行时编译。当 JIT 编译器完成第一次编译后- 其会将字节码对应的机器码保存下来,下次可以直接使用
- 这也解释了我们为什么经常会说 Java 是编译与解释共存的语言
类加载过程
- 在上面调用
java.exe
时,需要把类给加载到 JVM 中,使用类加载器进行加载
编译到加载整体流程
类加载过程
- 在硬盘上查找并通过 IO 读入字节码文件,加载到 JVM 内存的方法区当中
方法区中存放的哪些信息分别如下
- 方法区中的内容:类被加载到方法区中后主要包含,运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应 class 实例的引用等信息
- 类加载器的引用:类加载器属于是一个实例对象,存放在
堆
中,在方法区中指向类加载器的地址- class 实例的引用:类加载器在加载类信息放到方法区中后,这些字节码信息加在一起,有共同的特征和行为,会创建一个对应的 Class 类型的实例对象存放到堆中
验证
- 校验字节码文件的正确性
- 编译的字节码,有可能被修改,在加载之前要验证字节码的正确性
准备
- 给类的静态变量分配内存,并赋予默认值
- 静态变量是在类加载的时候就被执行的
解析
- 将符号引用替换为直接引用,该阶段会把一些静态方法替换为指向数据所存内存的指针或句柄等 (直接引用) 这个过程也称为是
静态链接
- 静态链接过程 (类加载期间完成)
- 动态链接是在程序运行期间完成的将符号引用替换为直接引用
初始化
- 对类的静态变量初始化为指定的值,执行静态代码块
类加载器
什么是类加载器 classLoader
- 负责将
.class
文件加载到内存中,并为之生成对应的Class
对象- 虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
类加载器分类
Bootstrap 根类加载器
- 也被称为引导类加载器,负责 Java 核心类的加载
- 比如
System
、String
等。在 JDK 中 JRE 的 lib 目录下rt.jar
文件中- c++ 来实现
Extension 扩展类加载器
- 负责 JRE 的扩展目录中 jar 包的加载
- 在 JDK 中 JRE 的 lib 目录下 ext 目录
- 由
sun.misc.Launcher$ExtClassLoader
实现- 负责加载
<JAVA_HOME>\lib\ext
或java.ext.dirs
系统变量指定的路径中的所有类库- 加载一些扩展的系统类,比如 XML、加密、压缩相关的功能类等
jdk9
的时候引入平台类加载器,废除了扩展类加载器
- JDK8 的主要加载
jre
、lib
的ext
,扩展 jar 包时使用- 这样操作并不推荐,所以废除。而 JDK9 有了模块化,更无需这种扩展加载器
- 加载一些平台相关的模块,比如
java.scripting
、java.compiler*
、java.corba*
App 应用程序类加载器
- 负责在 JVM 启动时加载来自 java 命令的 class 文件
- 以及 classpath 环境变量所指定的 jar 包和类路径
自定义加载器
- 负责加载用户自定义路径下的类包
各种类加载器代码演示,首先看 jdk1.8 在 1.8 当中还是叫扩展类加载器,看完了 1.8 在看 jdk11
/**
* @author BNTang
**/
public class Client {
public static void main(String[] args) {
// 根类加载器 c++ 实现的,打印为 null,因为它并不是属于 java 语言当中的
System.out.println(String.class.getClassLoader());
// 扩展类加载器
System.out.println(DNSNameService.class.getClassLoader());
// 应用程序类加载器
System.out.println(Client.class.getClassLoader());
}
}
如上 jdk1.8 的内容看完之后在 idea 当中切换 jdk 到 11:
切换之后发现 DNSNameService
找不到了,在 11 之后就变为了 平台类加载器
如下:
/**
* @author BNTang
**/
public class Client {
public static void main(String[] args) {
// 根类加载器 c++ 实现的,打印为 null,因为它并不是属于 java 语言当中的
System.out.println(String.class.getClassLoader());
// 平台类加载器
System.out.println(JavaCompiler.CompilationTask.class.getClassLoader());
// 应用程序类加载器
System.out.println(Client.class.getClassLoader());
}
}
双亲委派
- 加载某个类时会先委托父加载器寻找目标类,找不到,再委托上层父加载器加载
- 如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类
/**
* @author BNTang
**/
public class Client {
public static void main(String[] args) {
// 根类加载器,C++
System.out.println(String.class.getClassLoader());
// 扩展类加载器
System.out.println(DNSNameService.class.getClassLoader());
// 应用程序类加载器
System.out.println(Client.class.getClassLoader());
// 自定义加载器略
System.out.println("----------------------华丽的分割线----------------------");
// 获取应用程序类加载器的父加载器(扩展类加载器)
System.out.println(Client.class.getClassLoader().getParent());
// 获取应用程序类加载器的父加载器的父加载器(根类加载器, C++)
System.out.println(Client.class.getClassLoader().getParent().getParent());
}
}
🐤为什么要有双亲委派,为了系统类的安全
,jvm 如何认定两个对象同属于一个类型
- 都是用同名的类完成实例化的
- 两个实例各自对应的同名的类的加载器必须是同一个
- 比如两个相同名字的类,一个是用系统加载器加载的,一个扩展类加载器加载的,两个类生成的对象将被 jvm 认定为不同类型的对象
为了系统类的安全,类似 java.lang.Object
这种核心类,jvm 需要保证他们生成的对象都会被认定为同一种类型,基于双亲委派模型设计,那么 Java 中基础的类,类似 Object 类重复多次的问题就不会存在了,因为经过层层传递,加载请求最终都会被 Bootstrap ClassLoader 所响应,加载的 Object 类也会只有一个,否则如果用户自己编写了一个 java.lang.Object
类,并把它放到了 ClassPath 中,会出现很多个 Object 类,这样 Java 类型体系中最最基础的行为都无法保证,应用程序也将一片混乱。
🐱👓避免类的重复加载
- 当父亲已经加载了该类时,就没有必要子 ClassLoader 再加载一次
- 保证被加载类的唯一性
类加载源码分析
- 获取字节码对象
在类被加载到内存的时候创建的对象,通过此对象操作字节码信息
加载字节码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 查找有没有加载过
Class<?> c = findLoadedClass(name);
// 如果没有加载过
if (c == null) {
long t0 = System.nanoTime();
try {
// 让父加载器进行加载
if (parent != null) {
// 父加载同样调用此方法,进行加载
c = parent.loadClass(name, false);
} else {
// 如果父为空, 就直接调用bootstrap根加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 略
}
if (c == null) {
// 如果都没有找到
long t1 = System.nanoTime();
// 自己去找 如果想要自定义classload 只须要重写findClass
// 模板方法设计模式
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
类加载器父子关系源码分析
自定义类加载器
/**
* @author BNTang
*/
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
File f = new File("d:/java/test/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
int length = fis.available();
byte[] data = new byte[length];
fis.read(data);
fis.close();
// 把二进制流转成Class类对象
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
}
// throws ClassNotFoundException
return super.findClass(name);
}
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader();
Class<?> clazz = myClassLoader.loadClass("top.it6666.Person");
Method show = clazz.getMethod("show");
show.invoke(null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具