Java类加载机制与JVM启动过程详解
·
Java类加载机制与JVM启动过程详解
本文档整理了关于Java类加载过程、类加载器触发机制、类加载范围以及JVM启动方式的讨论内容,以问答形式呈现,并辅以清晰的要点和图示。
1. 如何记忆Java类加载的过程?
Java类加载主要分为五个阶段:加载、验证、准备、解析、初始化。为了便于记忆,可以采用以下方法:
记忆口诀
- 加载 → 链接 → 初始化(三大步)
- 链接又分为:验证 → 准备 → 解析
- 英文首字母:L V P R I
→ 联想句子:Let’s Very Patiently Run It(让我们非常耐心地运行它)
形象记忆:做一道菜
| 阶段 | 对应动作 | 解释 |
|---|---|---|
| 加载 | 去超市买菜 | 将类的二进制字节流读入内存,生成Class对象 |
| 验证 | 检查食材是否安全 | 确保字节流符合JVM规范,不会危害虚拟机安全 |
| 准备 | 洗菜、切菜 | 为静态变量分配内存并设置默认初始值(如0、null) |
| 解析 | 对照菜谱搭配 | 将符号引用替换为直接引用(类、方法、字段的具体内存地址) |
| 初始化 | 开火烹饪 | 执行静态代码块,给静态变量赋正确的初始值 |
每个阶段的关键词速记
- 加载:找class文件 → 读入内存 → 生成Class对象
- 验证:检查格式、语义、字节码安全性
- 准备:静态变量分配内存 + 默认值(注意:此时不是代码中赋的值)
- 解析:符号引用 → 直接引用
- 初始化:执行静态语句块、静态变量赋值(按代码顺序)
2. 类加载器是如何被触发的?触发顺序是怎样的?入口是main方法吗?
2.1 类加载器如何被触发?
类加载器本身是Java对象(除引导类加载器外),由JVM在启动阶段创建。类加载请求的产生时机包括:
- 遇到
new、getstatic、putstatic、invokestatic字节码指令(如new对象、访问静态字段、调用静态方法) - 反射调用(如
Class.forName()) - 初始化子类时触发父类加载(但父类可能已加载)
- JVM启动时加载包含
main()的主类 - 动态代理、Lambda表达式、JSP等动态生成类的场景
总结:类加载器并非主动触发,而是程序运行需要某个类时,JVM通过类加载器加载该类。
2.2 类加载器的触发顺序(双亲委派模型)
JVM启动时按以下顺序创建类加载器:
- 引导类加载器(C++实现):加载核心类库(
<JAVA_HOME>/lib) - 扩展类加载器:由引导类加载器加载
ExtClassLoader,负责加载<JAVA_HOME>/lib/ext - 应用程序类加载器:由扩展类加载器加载
AppClassLoader,负责加载classpath下的类
当一个类加载请求出现时,请求的委派顺序是从子到父向上传递,而实际加载尝试的顺序是从父到子向下查找:
- 应用程序类加载器收到请求 → 委派给扩展类加载器 → 委派给引导类加载器
- 引导类加载器尝试加载(若找到则返回)→ 否则扩展类加载器尝试加载 → 否则应用程序类加载器尝试加载
2.3 main方法是入口吗?
main方法所在的类是JVM启动后第一个被显式加载的用户类,但类加载器的触发在main之前就已开始(加载主类本身)。main方法执行过程中,遇到其他类的使用(如new、反射等)会继续触发类加载。- 因此,
main方法是程序执行的入口,但不是类加载的唯一触发入口。
3. 引导类加载器和扩展类加载器会在JVM启动时加载所有类吗?
不会。 两者均遵循**按需加载(懒加载)**原则,只加载程序运行所必需的最核心类,以节省内存和加快启动速度。
3.1 具体加载范围
| 类加载器 | 加载路径 | 启动时是否加载所有类? | 启动时加载了哪些? |
|---|---|---|---|
| 引导类加载器 | jre/lib(如rt.jar) |
否 | 仅加载JVM和程序启动必需的核心类(如java.lang.Object) |
| 扩展类加载器 | jre/lib/ext |
否 | 几乎不加载,直到程序中第一次使用到ext中的某个类 |
3.2 为什么不是全部加载?
- 节省内存:JDK包含成千上万个类,全部加载会消耗大量内存。
- 加快启动速度:加载类需要磁盘I/O和字节码解析,懒加载能显著提升启动时间。
- 按需使用:大多数程序只用到一小部分核心类。
3.3 验证方法
添加JVM启动参数:-XX:+TraceClassLoading
运行一个简单的HelloWorld程序,观察加载日志,你会发现只有少量核心类被加载,而非整个rt.jar。
4. JVM启动是如何触发的?是通过运行java -jar命令吗?
是的,运行java命令(包括java -jar)是触发JVM启动最常见的方式。
4.1 java -jar命令的执行流程
- 操作系统shell调用
java可执行文件(启动器)。 - 启动器创建一个新的操作系统进程,并加载JVM动态链接库(如
jvm.dll或libjvm.so)。 - 初始化JVM内部环境:创建引导类加载器、内存空间、垃圾回收器等。
- 解析命令行参数:
- 若使用
-jar,从JAR包的META-INF/MANIFEST.MF中读取Main-Class属性作为主类。 - 若直接指定类名(如
java com.example.Main),则将该类作为主类。
- 若使用
- 通过应用程序类加载器加载主类,并执行其
main方法。
4.2 其他触发JVM启动的方式
- 通过JNI调用:在本地代码(C/C++)中调用
JNI_CreateJavaVM函数,在现有进程中嵌入JVM(如浏览器运行Applet)。 - 通过特定工具或容器:如
javaw(Windows无控制台)、Docker容器启动Java应用等。
4.3 与类加载过程的关系
JVM启动 → 创建引导类加载器 → 加载核心类 → 创建扩展和应用程序类加载器 → 加载主类 → 按需加载其他类(遵循双亲委派模型)。
java -jar是整个链条的起点。
总结
- 类加载过程:加载、验证、准备、解析、初始化(可用口诀或形象记忆)。
- 类加载器触发:按需触发,遵循双亲委派模型,请求向上委派,加载尝试向下进行。
- 类加载范围:引导和扩展类加载器均懒加载,不会预加载所有类。
- JVM启动:通过
java命令创建JVM进程,初始化环境后加载主类并执行main方法。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)