找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

407

积分

0

好友

47

主题
发表于 2025-12-29 00:03:34 | 查看: 23| 回复: 0

要深入理解Java程序的运行,就必须掌握类加载机制。它负责将.class字节码文件转换JVM内存中的运行时数据结构,是整个JVM运行的基石。下图清晰地勾勒出了Java类加载机制的核心脉络:

Java类加载机制思维导图

一、Java程序的宏观执行流程

一个Java程序的旅程始于.java源文件。javac编译器将其编译为平台无关的.class字节码文件。随后,Java虚拟机(JVM)加载这些字节码,并通过解释执行或即时编译(JIT)将其转换为特定操作系统的机器指令来运行。其宏观流程可概括如下:

Java字节码执行流程图

二、类的生命周期与类加载过程详解

我们常说的“类加载”是一个广义概念,它对应着类生命周期中的“加载”和“连接”两大阶段。下图完整展示了从源代码到类卸载的全过程:

类的生命周期流程图

具体而言,类加载过程可细分为五个紧密衔接的核心阶段。

1. 加载(Loading)

加载是类加载过程的第一步,主要完成三件事:

  • 通过类的全限定名获取定义此类的二进制字节流。
  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  • 在内存中生成一个代表该类的java.lang.Class对象,作为访问方法区中该类数据的入口。

注意:Class文件的来源非常广泛,不仅限于本地文件系统,还可以来自网络、ZIP/JAR包、运行时动态生成(如动态代理)等。

2. 验证(Verification)

此阶段目的是确保Class文件的字节流信息符合JVM规范,保证其不会危害虚拟机自身安全。它包含四个检验动作:

  • 文件格式验证:验证字节流是否符合Class文件格式规范,如魔数、主次版本号等。
  • 元数据验证:对字节码描述的信息进行语义分析,确保符合Java语言规范,例如检查是否有父类、是否继承了final类等。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法且符合逻辑的,例如保证跳转指令不会跳转到方法体之外。
  • 符号引用验证:发生在解析阶段,确保后续的解析动作能正常执行,例如检查通过全限定名是否能找到对应的类、访问权限是否合法等。

3. 准备(Preparation)

此阶段正式为类变量(被static修饰的变量) 分配内存(在方法区中)并设置初始值。

  • 重要概念:此阶段设置的是数据类型的零值。例如,对于public static int value = 123;,在准备阶段后value的初始值是0,而非123。真正的赋值动作putstatic指令被编译在<clinit>()方法中,于初始化阶段执行。
  • 特殊情况:如果类字段被static final修饰(即常量),且在字段属性表中存在ConstantValue属性,那么在准备阶段变量就会被直接初始化为指定的值。例如,public static final int value = 123;在准备阶段后,value的值就是123

4. 解析(Resolution)

此阶段JVM将常量池内的符号引用替换为直接引用

  • 符号引用:以一组符号(如全限定名)来描述所引用的目标,与虚拟机内存布局无关。
  • 直接引用:可以是直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,与虚拟机内存布局相关。
  • 解析策略
    • 静态解析:如果符号引用在加载时已明确具体目标,则在解析阶段完成替换。
    • 动态解析:对于编译期无法确定的引用(如多态、接口实现),则推迟到第一次实际使用时(运行时)再解析。

5. 初始化(Initialization)

这是类加载的最后一步,才开始真正执行类中定义的Java程序代码(字节码)。JVM会执行由编译器自动收集类中所有类变量赋值动作静态语句块(static{}块)合并生成的<clinit>()方法。JVM保证在子类的<clinit>()方法执行前,其父类的<clinit>()方法已经执行完毕。

三、Java类加载器

类加载器是实现“通过类的全限定名获取该类的二进制字节流”这个动作的代码模块。JVM提供了三层类加载器,它们以组合关系协同工作,是JVM系统的重要组成部分:

  1. 启动类加载器(Bootstrap Class Loader)

    • 由C++实现,是JVM自身的一部分。
    • 负责加载<JAVA_HOME>/lib目录下,或被-Xbootclasspath参数指定路径中的核心类库(如rt.jar)。
    • 是所有类加载器的父加载器(顶层)。
  2. 扩展类加载器(Extension Class Loader)

    • 由Java实现,对应sun.misc.Launcher$ExtClassLoader类。
    • 负责加载<JAVA_HOME>/lib/ext目录下,或java.ext.dirs系统变量指定路径中的所有类库。
  3. 应用程序类加载器(Application Class Loader)

    • 由Java实现,对应sun.misc.Launcher$AppClassLoader类。
    • 负责加载用户类路径(ClassPath)上指定的类库。它是程序中默认的类加载器。

除了这三个系统类加载器,开发者还可以通过继承java.lang.ClassLoader类,定制自己的类加载器,以满足如热部署、从网络或加密文件中加载类等特殊需求。

四、双亲委派模型

1. 工作流程

双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应有自己的父类加载器(通过组合而非继承实现)。其工作流程如下:
当一个类加载器收到类加载请求时,它首先不会尝试自己加载,而是将请求委派给父类加载器去完成。每一层类加载器都如此处理,因此所有加载请求最终都应传送到顶层的启动类加载器。只有当父加载器反馈自己无法完成加载请求(在搜索范围内未找到所需类)时,子加载器才会尝试自己去加载。

2. 核心优势

  • 避免类的重复加载:确保一个类在JVM全局唯一。父加载器加载过后,子加载器不会再加载。
  • 保证程序安全:防止核心API库被篡改。例如,用户自定义java.lang.Object类,双亲委派机制会保证最终由启动类加载器加载核心的Object类,从而阻止恶意代码注入。

3. 破坏双亲委派模型

双亲委派模型并非强制性约束,在特定场景下会被打破,例如:

  • SPI(Service Provider Interface)机制:如JDBC。核心接口在rt.jar中由启动类加载器加载,但具体实现(如MySQL驱动)在ClassPath下,需由应用类加载器加载。为此引入了线程上下文类加载器来实现逆向委托。
  • OSGiJNDI等模块化或热部署技术,也需要动态调整类加载器的委托关系,以实现更灵活的类加载策略。



上一篇:C#/.NET开发者精通指南:从核心特性到高性能编程实践
下一篇:SC-400合规报告:掌握数据治理7大指标与应用实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-10 09:07 , Processed in 0.301523 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表