Java类加载机制从.class文件到JVM运行时

 更新时间:2025年12月02日 08:45:10   作者:RTower  
本文详细介绍了Java类加载机制,包括Java程序的运行流程、类的生命周期与类加载、类加载器和双亲委派模型,通过类加载器将.class文件加载到内存中,并转换为JVM可以使用的运行时数据结构,最终实现类的初始化,感兴趣的朋友跟随小编一起看看吧

首先先贴上一张Java类加载的思维导图

一、Java 程序运行流程

在Java中,一个程序的起点是.java源文件,它经由javac编译器编译为字节码.class文件。随后,Java虚拟机(JVM)负责加载这些字节码文件,并将其转换为与操作系统交互的机器指令来执行。我们可以用以下流程图来概括这个宏观过程:

二、类的生命周期与类加载

而我们今天要深入剖析的,正是JVM将.class文件中的二进制数据读入内存,并转换成JVM可以使用的运行时数据结构的整个过程——类加载。要理解类加载,就离不开类的完整生命周期。下图展示了包含类生命周期的完整视图:

从上图可见,我们通常所说的“类加载”是一个广义概念,它主要指生命周期中“加载”和“连接”两大阶段,具体可细分为以下五个核心部分:

  • 加载
    • 加载是“类加载”过程的第一步。主要完成三件事:
  • 注意:Class文件的来源非常广泛,不仅仅是本地文件系统,还可以来自ZIP/JAR包、网络、运行时计算生成(动态代理)或其它文件(JSP生成)等。

    • 通过类的全限定名获取定义此类的二进制字节流。
    • 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
    • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区该类的各种数据的访问入口。
  • 验证
    • 验证是连接阶段的第一步,目的是确保Class文件的字节流信息符合JVM规范,不会危害JVM自身安全。它包含四个检验动作:
  • 文件格式验证:验证字节流是否符合Class文件格式规范(如魔数、主次版本号等),这一步在加载阶段即可发生。
  • 元数据验证:对字节码描述的信息进行语义分析,确保符合Java语言规范(如是否有父类、是否继承了final类等)。
  • 字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的(如保证跳转指令不会跳转到方法体以外的字节码上)。
  • 符号引用验证:发生在解析阶段,确保解析动作能正常执行(如通过全限定名能找到对应的类、访问性是否合法等)。
  • 准备
    • 准备阶段是正式为类变量(被static修饰的变量) 分配内存并设置初始值的阶段。这些变量所使用的内存都在方法区中进行分配。
  • 重要概念:此阶段设置的通常是数据类型的零值。例如,public static int value = 123;,在准备阶段后,value的初始值是0,而非123。将value赋值为123putstatic指令是在程序编译后,存放于<clinit>()类构造器方法中,所以赋值的动作是在初始化阶段才会执行。
  • 特殊情况:如果类字段的字段属性表中存在ConstantValue属性(即被static final修饰的常量),那么在准备阶段变量便会被初始化为指定的值。例如,public static final int value = 123;在准备阶段后,value的值就是123
  • 解析
    • 解析阶段是JVM将常量池内的符号引用替换为直接引用的过程。
  • 符号引用:以一组符号来描述所引用的目标,与JVM实现的内存布局无关。
  • 直接引用:可以是直接指向目标的指针、相对偏移量或能间接定位到目标的句柄,与JVM的内存布局相关。
  • 解析策略
  • 静态解析:如果符号引用在加载时就已经明确具体的目标,那么在解析阶段就可以完成替换。
  • 动态解析:对于一些在编译期无法确定的引用(如多态、接口实现),则要推迟到第一次实际使用时(运行时)再完成解析。
  • 初始化
    • 这是类加载过程的最后一步。此阶段才真正开始执行类中定义的Java程序代码(或字节码)。JVM会执行<clinit>()方法,该方法是由编译器自动收集类中的所有类变量的赋值动作静态语句块中的语句合并产生的。JVM会保证在子类的<clinit>()方法执行前,父类的<clinit>()方法已经执行完毕。

三、Java类加载器

类加载器是实现“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作的代码模块。

JVM提供了三层类加载器,它们之间以组合关系协同工作:

  • 启动类加载器(Bootstrap Class Loader) :
    • 由C++实现,是JVM自身的一部分。
    • 负责加载<JAVA_HOME>/lib目录下,或被-Xbootclasspath参数指定的路径中的核心类库(如rt.jarcharsets.jar等)。
    • 它是所有类加载器的父加载器。
  • 扩展类加载器(Extension Class Loader) :
    • 由Java实现,是sun.misc.Launcher$ExtClassLoader类。
    • 负责加载<JAVA_HOME>/lib/ext目录下,或java.ext.dirs系统变量所指定的路径中的所有类库。
    • 它是应用程序类加载器的父加载器。
  • 应用程序类加载器(Application Class Loader) :
    • 由Java实现,是sun.misc.Launcher$AppClassLoader类。
    • 负责加载用户类路径(ClassPath)上所指定的类库。它是程序中默认的类加载器。

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

四、双亲委派模型

1. 工作流程

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器(这里的父子关系一般通过组合而非继承来实现)。它的工作流程是:

当一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。

只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

2. 核心优势

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

3. 破坏双亲委派模型

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

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

到此这篇关于Java类加载机制从.class文件到JVM运行时的文章就介绍到这了,更多相关java类加载机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 关于spring boot整合kafka+注解方式

    关于spring boot整合kafka+注解方式

    这篇文章主要介绍了关于spring boot整合kafka+注解方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringBoot配置嵌入式Servlet容器和使用外置Servlet容器的教程图解

    SpringBoot配置嵌入式Servlet容器和使用外置Servlet容器的教程图解

    这篇文章主要介绍了SpringBoot配置嵌入式Servlet容器和使用外置Servlet容器的教程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java并发编程中的Exchanger解析

    Java并发编程中的Exchanger解析

    这篇文章主要介绍了Java并发编程中的Exchanger解析,Exchanger用于线程间数据的交换,它提供一个同步点,在这个同步点,两个线程可以交换彼此的数据,这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,需要的朋友可以参考下
    2023-11-11
  • idea一键部署SpringBoot项目jar包到服务器的实现

    idea一键部署SpringBoot项目jar包到服务器的实现

    我们在开发环境部署项目一般通过idea将项目打包成jar包,然后连接linux服务器,将jar手动上传到服务中,本文就来详细的介绍一下步骤,感兴趣的可以了解一下
    2023-12-12
  • 浅析java 希尔排序(Shell)算法

    浅析java 希尔排序(Shell)算法

    这篇文章主要介绍了浅析java 希尔排序(Shell)算法的原理以及示例,需要的朋友可以参考下
    2015-02-02
  • java 利用反射获取内部类静态成员变量的值操作

    java 利用反射获取内部类静态成员变量的值操作

    这篇文章主要介绍了java 利用反射获取内部类静态成员变量的值操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • 如何通过Kaptcha在Web页面生成验证码

    如何通过Kaptcha在Web页面生成验证码

    这篇文章主要介绍了如何通过Kaptcha在Web页面生成验证码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java部分序列化之关键字transient使用及说明

    Java部分序列化之关键字transient使用及说明

    Java序列化将对象转为字节序列用于存储或传输,需实现Serializable接口,transient关键字可排除敏感字段或不可序列化对象,避免反序列化时恢复无效数据或泄露信息
    2025-09-09
  • 启动springboot项目时报错:无法访问org.springframework.web.bind.annotation.GetMapping …具有错误的版本 61.0,应为52.0​的解决方案

    启动springboot项目时报错:无法访问org.springframework.web.bind.annotatio

    这篇文章给大家分享了启动springboot项目时报错:​无法访问org.springframework.web.bind.annotation.GetMapping …具有错误的版本 61.0,应为52.0​的解决方案,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • java理论基础Stream reduce实现集合元素归约

    java理论基础Stream reduce实现集合元素归约

    这篇文章主要为大家介绍了java理论基础Stream reduce实现集合元素归约示例详解有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03

最新评论