Java 编译之后生成的字节码文件,进入执行阶段,首先要进入 Java 虚拟机被加载。
那么加载的过程是什么样?不同层级的类被加载进 元空间(Metaspace) 时,它们分别要被哪几个加载器加载呢?
一、JVM 的四种类加载器
JVM 并不是直接把类“吃掉”,而是要分层次、分角色地去加载不同来源的类。主要有以下几种加载器:
-
启动类加载器(Bootstrap ClassLoader)
-
作用:加载核心类库(比如
java.lang.*
、java.util.*
等)。 -
举例:加载
String
、Object
、Integer
。 -
特点:由 C++ 实现,不是普通 Java 对象,看不见也碰不到。
-
-
扩展/平台类加载器(Extension / Platform ClassLoader)
-
作用:加载 JDK 扩展目录中的类库(早期是
jre/lib/ext
,Java 9 之后叫 Platform ClassLoader,加载一些平台模块)。 -
举例:
javax.crypto.*
、javax.sql.*
。
-
-
应用程序类加载器(Application / System ClassLoader)
-
作用:加载应用的类路径(Classpath)下的类,也就是我们写的普通 Java 代码,以及依赖的第三方 jar 包。
-
举例:你写的
com.xxx.Main
、项目中引入的 Spring 框架。
-
-
自定义类加载器(Custom ClassLoader)
-
作用:用户自己写的,通常用来实现特殊需求(比如从网络、数据库、加密文件里加载 class)。
-
举例:Tomcat、Spring Boot 就会自定义类加载器来隔离不同模块的 class。
-
二、类加载器是干什么的?
一句话:类加载器的职责就是把 .class
文件读进来,转化成 JVM 可以理解的 Class
对象,并放到元空间(Metaspace)里。
换句话说,它就像是“搬运工 + 翻译机”:
-
搬运工:找到字节码文件(无论是本地磁盘、网络、jar 包)。
-
翻译机:把字节码转成 JVM 内部的运行时数据结构,存放在元空间,等待方法区等运行时结构使用。
三、双亲委派机制
既然有这么多加载器,那问题来了:加载的时候到底谁来干活?
这里就有一个“啃老”的机制——双亲委派机制(Parent Delegation Model)。
流程是这样的:
-
当某个类加载器接到加载请求时,它自己先不急着动手。
-
它会先把请求“往上交”,让自己的父类加载器去尝试加载。
-
这样一层层往上,直到 启动类加载器(Bootstrap)。
-
如果父类加载器都说:“我不认识这个类”,请求才会一层层退回来,最后由自己来加载。
打个比方:
就像写作业不会的题,先问爸爸,爸爸不会问爷爷,爷爷不会问太爷爷。
如果全家都不会,最后只能靠自己硬着头皮去写。
四、双亲委派的意义
为什么要搞这么一套“啃老”的机制呢?
主要有三个原因:
-
避免重复加载
-
父类加载器如果已经加载过某个类,子类加载器就不用重复加载了。
-
-
保证核心类的安全性
-
假如没有双亲委派,你可以写一个自定义的
java.lang.String
,试图“顶替”JDK 的String
。 -
有了双亲委派,JVM 会优先用 Bootstrap 已经加载的核心
String
,屏蔽掉用户代码的冒充。
-
-
实现模块化与隔离
-
应用类和第三方库只管加载自己的,不会干扰到 JVM 核心类。
-
各个 Web 容器、自定义 ClassLoader 也能通过这套机制实现类隔离。
-
五、加载类的最终目的
类加载的本质目的就是:
把字节码文件(.class)转成 JVM 内部的 Class 对象,并放入元空间(Metaspace)。
元空间中保存了类的方法、字段、常量池、运行时结构等信息,供 JVM 在运行期使用。
真正的对象实例化(分配在堆上)要等到 new 的时候才发生。