Featured image of post JVM_00_JVM整体结构与类加载器

JVM_00_JVM整体结构与类加载器

🌏Java工程师 JVM 🎯 这系列文章用于记录 JVM_00_JVM整体结构与类加载器 相关的学习和总结

JVM整体结构

类加载子系统

核心作用

类加载子系统是JVM的基石之一,它主要负责以下三件核心事情:

  1. 加载(Loading): 查找并导入二进制字节流(.class文件)。
  2. 链接(Linking): 将已加载的类数据合并到JVM运行时环境中,为其分配内存空间,并处理一些前期准备工作。链接本身又可分为三个子步骤:
    • 验证(Verification): 确保被加载的类的字节流符合《Java虚拟机规范》的约束,保证其正确性和安全性,不会危害虚拟机自身。这是一道重要的安全防线。
    • 准备(Preparation): 为类的静态变量分配内存并设置其初始值(即数据类型的零值,如00Lnullfalse等)。注意,这里不是程序代码中赋予的值(public static int value = 123; 在此阶段后value0,而非123)。
    • 解析(Resolution): 将常量池内的符号引用转换为直接引用的过程。可以简单理解为将变量名、方法名等“符号”替换为具体的内存地址或偏移量。
  3. 初始化(Initialization): 执行类的构造器 <clinit>() 方法的过程。这个方法是由编译器自动收集类中的所有静态变量的赋值动作静态语句块中的语句合并产生的。在这个阶段,静态变量才会被赋予代码中定义的真正值(value在此阶段被赋值为123)。

注意:加载、连接、初始化的开始顺序是确定的,但执行过程是交叉、混合的,并非完全串行。通常的实现(如HotSpot虚拟机)是:在类加载完成后立即进行验证和准备,而将解析阶段推迟到符号引用真正被使用(主动引用)之前。它可以在初始化之后再进行,这是为了支持Java的动态绑定(晚期绑定)特性。

总结其宏观作用:类加载子系统是在Java中实现“代码动态加载和执行”的关键,它使得Java无需像C/C++那样在编译时进行全部链接,从而实现了高度的灵活性和动态扩展性(如OSGi、热部署等技术都基于此)。JVM严格遵守懒加载原则。类的加载、连接和初始化是在类被首次主动使用时才触发的,而不是在程序启动时就全部加载。这极大地节省了内存开销。

内部结构

类加载子系统并非一个简单的模块,它由以下几个关键组件协同工作:

  1. 类加载器(ClassLoader): 这是子系统的“执行引擎”,是实际完成加载动作的组件。JVM采用了分层、委托的类加载器架构:
    • 启动类加载器(Bootstrap ClassLoader): 最顶层的加载器,由C++实现(HotSpot虚拟机中),负责加载<JAVA_HOME>/lib目录下的核心类库(如rt.jarcharsets.jar等)。它无法被Java程序直接引用。
    • 扩展类加载器(Extension ClassLoader): 由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>/lib/ext目录下,或由java.ext.dirs系统变量指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader): 由sun.misc.Launcher$AppClassLoader实现。它是ClassLoader.getSystemClassLoader()的返回值,因此也称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库。开发者编写的代码默认由它加载。
    • 自定义类加载器(Custom ClassLoader): 开发者通过继承java.lang.ClassLoader类创建的加载器,可以实现一些特殊的加载需求(如加密解密、从网络加载、热部署等)。
  2. 运行时数据区(Runtime Data Areas)的相关部分: 类加载子系统本身不存储数据,加载后的类信息(包括类名、方法、字段、常量池等元数据)被存储在方法区(Method Area)。而在堆(Heap) 中,会创建一个代表这个类的java.lang.Class对象,作为方法区中该类各种数据的访问入口。

java.lang.ClassLoader与其他三种类加载器的关系?

作用原理

**双亲委派模型(Parents Delegation Model)**是类加载子系统最核心的工作原则。它的工作流程如下:

当一个类加载器收到加载请求时,它不会首先自己去尝试加载这个类,而是会:

  1. 自底向上,委托父级(“双亲”): 将这个请求委派给父类加载器去完成。 每一层的类加载器都会重复这个动作,直到传送到最顶层的启动类加载器

  2. 自顶向下,尝试加载: 当遇到的第一个父级类加载器可以加载(在自己的加载路径搜索到当前要加载的类信息),便进行加载并返回结果;如果找i不到,则将职责退回给下一级父加载器;

  3. (自定义类加载器)自身尝试加载

  • 如果父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类),子加载器才会尝试自己去加载
  • 如果父类加载器成功加载,则直接返回结果。

双亲委派模型的作用和优势:

  • 保证Java核心库的类型安全: 核心类(如java.lang.Object)总是由启动类加载器加载,确保了无论哪个类加载器要加载这个类,最终都会委派到最顶层的Bootstrap加载器。这避免了用户自定义一个java.lang.Object类并被加载,从而破坏了Java体系的基础行为。
  • 避免类的重复加载: 通过委派机制,一个类一旦被某个加载器加载,其他的加载器就没有机会再加载它,保证了类的全局唯一性。
  • 保证了Java程序的稳定运行

打破双亲委派: 在某些复杂场景下(如JNDI、JDBC、OSGi、代码热替换等),需要打破这个模型。实现方式通常是重写ClassLoaderloadClass()方法(默认实现了双亲委派逻辑)或findClass()方法。高级开发者需要理解如何以及为何要打破它。

Licensed under CC BY-NC-SA 4.0
最后更新于 2024年1月16日