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在启动阶段创建。类加载请求的产生时机包括:

  • 遇到newgetstaticputstaticinvokestatic字节码指令(如new对象、访问静态字段、调用静态方法)
  • 反射调用(如Class.forName()
  • 初始化子类时触发父类加载(但父类可能已加载)
  • JVM启动时加载包含main()的主类
  • 动态代理、Lambda表达式、JSP等动态生成类的场景

总结:类加载器并非主动触发,而是程序运行需要某个类时,JVM通过类加载器加载该类。

2.2 类加载器的触发顺序(双亲委派模型)

JVM启动时按以下顺序创建类加载器:

  1. 引导类加载器(C++实现):加载核心类库(<JAVA_HOME>/lib
  2. 扩展类加载器:由引导类加载器加载ExtClassLoader,负责加载<JAVA_HOME>/lib/ext
  3. 应用程序类加载器:由扩展类加载器加载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命令的执行流程

  1. 操作系统shell调用java可执行文件(启动器)。
  2. 启动器创建一个新的操作系统进程,并加载JVM动态链接库(如jvm.dlllibjvm.so)。
  3. 初始化JVM内部环境:创建引导类加载器、内存空间、垃圾回收器等。
  4. 解析命令行参数:
    • 若使用-jar,从JAR包的META-INF/MANIFEST.MF中读取Main-Class属性作为主类。
    • 若直接指定类名(如java com.example.Main),则将该类作为主类。
  5. 通过应用程序类加载器加载主类,并执行其main方法。

4.2 其他触发JVM启动的方式

  • 通过JNI调用:在本地代码(C/C++)中调用JNI_CreateJavaVM函数,在现有进程中嵌入JVM(如浏览器运行Applet)。
  • 通过特定工具或容器:如javaw(Windows无控制台)、Docker容器启动Java应用等。

4.3 与类加载过程的关系

JVM启动 → 创建引导类加载器 → 加载核心类 → 创建扩展和应用程序类加载器 → 加载主类 → 按需加载其他类(遵循双亲委派模型)。

java -jar是整个链条的起点。


总结

  • 类加载过程:加载、验证、准备、解析、初始化(可用口诀或形象记忆)。
  • 类加载器触发:按需触发,遵循双亲委派模型,请求向上委派,加载尝试向下进行。
  • 类加载范围:引导和扩展类加载器均懒加载,不会预加载所有类。
  • JVM启动:通过java命令创建JVM进程,初始化环境后加载主类并执行main方法。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐