反射中的Class.forName和ClassLoader:解析Java加载机制的差异

一、背景

我写文章很少交代写作背景,这个文章之所以特别说明写作背景,因为它确实很特别。

  • 故事最开始是线上机器人突然告警一个服务被重启了。。。
  • 当时是星期天,线上机器没有发生过故障,所以我们在群里问,谁重启了该服务。

  • 结果最后是没人重启

  • 担心线上机器被攻击或者存在安全漏洞,急急忙忙连上去看了下,最终发现服务稳定运行很久了,确实没有人重启。

那么问题来了,这个告警是怎么来的?

告警的代码如上,乍看这个代码,是没有毛病的。那么怎么会突然告警呢?于是我研究了下这个鬼玩意,希望得到答案。

二、反射中的Class.forName

Class.forName是一个静态方法,它通过提供类的全限定名作为参数来加载类。这个方法在Java早期版本中被广泛使用,它不仅加载类,还会执行该类的静态初始化块。这意味着,通过Class.forName加载的类会被立即初始化。

2.1 Class.forName的语法

Class.forName的语法如下:

java 复制代码
Class.forName("com.example.MyClass");

2.2 Class.forName的加载过程

Class.forName是一个静态方法,它根据提供的类名加载并返回对应的Class对象。它会执行以下操作:

1、首先,Class.forName方法根据提供的类名尝试加载该类。它会查找类路径上的所有类加载器,按顺序尝试加载类,直到找到匹配的类或所有类加载器都无法加载该类。

2、如果成功找到并加载了类,返回对应的Class对象。否则,抛出ClassNotFoundException异常。

3、在加载类的过程中,会执行类的静态初始化。这包括执行静态初始化块和静态变量的初始化。静态初始化在类加载的过程中只会执行一次。

4、如果Class.forName方法的第二个参数指定为true,则静态初始化代码会被执行。如果参数指定为false或省略,则只加载类,而不执行静态初始化。这种延迟加载的方式可以用于避免不必要的静态初始化,直到类被实际使用时才执行。

注意:Class.forName方法还有一个重载形式,可以指定类加载器。例如,Class.forName(className, true, classLoader)可以使用指定的类加载器加载类,并执行静态初始化。

graph TD A[开始] --> B[尝试加载指定类名的类] B --> |成功加载| C[返回对应的Class对象] B --> |加载失败| D[抛出ClassNotFoundException异常] C --> E[执行类的静态初始化] E --> F[执行静态初始化块和静态变量初始化] E --> G[根据第二个参数决定是否执行静态初始化代码] G --> |执行静态初始化代码| H[加载并初始化后的Class对象] G --> |不执行静态初始化代码| H[加载并初始化后的Class对象] H --> I[结束] D --> I[结束]

三、反射中ClassLoader

ClassLoader是一个抽象类,用于实现类的加载。每个类都有一个关联的ClassLoader对象,负责加载该类。ClassLoader提供了一组方法用于在运行时加载类,并返回对应的Class对象。

ClassLoader的主要职责是从指定的源(如文件系统、网络或其他来源)加载类的字节码,并将其转换为可在Java虚拟机中运行的类。它还负责解析类的依赖关系,即加载所需的其他类。

ClassLoader可以有不同的实现,如系统类加载器、扩展类加载器和自定义类加载器。每个加载器都有一个父加载器,当加载器无法找到类时,它会委托给其父加载器进行加载。这种层级结构支持类加载器的继承和委派模型。

ClassLoader是一个类加载器的抽象基类,它负责动态加载Java类。ClassLoader提供了更细粒度的控制,可以根据需要选择性地加载类。它是Java中类加载的核心组件之一。

3.1 ClassLoader的语法

ClassLoader的语法如下:

java 复制代码
ClassLoader classLoader = MyClass.class.getClassLoader();

3.2 Java中三种主要的ClassLoader

  • Bootstrap ClassLoader :是Java虚拟机内置的ClassLoader,负责加载核心Java类库,通常由本地代码实现。

  • Extension ClassLoader :负责加载Java的扩展类库,它是由sun.misc.Launcher$ExtClassLoader实现的。

  • System ClassLoader :也称为Application ClassLoader,负责加载应用程序类。它是ClassLoader的子类,由sun.misc.Launcher$AppClassLoader实现。

3.3 ClassLoader的加载过程

ClassLoader的加载步骤和执行过程如下:

1、首先,ClassLoader会根据给定的类名尝试加载类。加载器会按照一定的规则搜索类文件,可以是文件系统、网络或其他来源。

2、如果找到类文件,ClassLoader将读取类文件的字节码,并将其转化为可在Java虚拟机中运行的类的定义。

3、在加载类的过程中,ClassLoader会解析类的依赖关系。这意味着它会查找并加载该类所依赖的其他类。通常,ClassLoader会委托给父加载器来加载依赖的类。

4、如果父加载器无法加载依赖的类,ClassLoader会尝试自己加载依赖的类。

5、在加载类的过程中,ClassLoader会执行类的静态初始化。这包括执行静态初始化块和静态变量的初始化。静态初始化只会在类首次加载时执行一次。

6、加载完成后,ClassLoader会返回表示该类的Class对象,可以用于进一步操作,如实例化对象、调用方法等。

注意:ClassLoader可以有不同的实现,如系统类加载器、扩展类加载器和自定义类加载器。每个加载器都有一个父加载器,当加载器无法找到类时,它会委托给父加载器进行加载。这种层级结构支持类加载器的继承和委派模型。

graph TD A(开始) B(尝试加载指定类名的类) C(搜索并读取类文件的字节码) D(解析类的依赖关系 委托给父加载器加载依赖的类) E(父加载器加载依赖的类成功) F(是) G(执行类的静态初始化) H(返回加载完成后的Class对象) I(否) J(尝试自己加载依赖的类) K(加载依赖的类成功) L(是) M(执行类的静态初始化) N(返回加载完成后的Class对象) O(否) P(抛出ClassNotFoundException异常) Q(结束) A --> B B --> C C --> D D --> E E --> F F --> G G --> H E --> I I --> J J --> K K --> L L --> M M --> N K --> O O --> P P --> Q I --> Q

四、Class.forName和ClassLoader加载机制的差异

看完上面发现,Class.forNameClassLoader虽然都与类的加载有关,但在实现和应用方面存在着明显的差异。了解它们的区别和特性,有助于开发人员更好地理解Java反射机制,并在需要的时候选择适当的加载方式。

graph LR; Q(差异) A(功能) --> B(Class.forName加载类并执行静态初始化块) A --> C(ClassLoader仅负责加载类) D(默认行为) --> E(Class.forName默认使用调用者的ClassLoader加载类) D --> F(ClassLoader可以显式指定加载类的ClassLoader) G(异常处理) --> H(Class.forName类未找到时抛出ClassNotFoundException) G --> I(ClassLoader类未找到时返回null) J(应用场景) --> K(Class.forName用于实现插件机制 动态加载特定类) J --> L(ClassLoader用于自定义ClassLoader实现类加载定制化需求) Q --> A Q --> D Q --> G Q --> J style A fill:#FFFFE0,stroke:#FFFFE0,stroke-width:2px style B fill:#98FB98,stroke:#98FB98,stroke-width:2px style C fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px style E fill:#98FB98,stroke:#98FB98,stroke-width:2px style F fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px style H fill:#98FB98,stroke:#98FB98,stroke-width:2px style I fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px style K fill:#98FB98,stroke:#98FB98,stroke-width:2px style L fill:#B2FFFF,stroke:#B2FFFF,stroke-width:2px style D fill:#FFA07A,stroke:#FFA07A,stroke-width:2px style G fill:#E6E6FA,stroke:#E6E6FA,stroke-width:2px style J fill:#EEDD82,stroke:#EEDD82,stroke-width:2px

五、运行中的程序,什么情况可能重新加载类呢?

在运行中的程序中,以下情况可能触发重新加载类:

1、修改类文件:如果在程序运行过程中修改了类文件(例如在开发环境中进行了代码修改),并且存在一种机制能够检测到文件的变化,那么程序可能会重新加载该类。

2、热部署机制 :某些特定的开发框架或工具(如Spring BootDevTools)具有热部署机制,它们会监控类文件的变化,并在检测到变化时自动重新加载类,以便应用程序能够即时获得代码更改的效果。

3、动态更新机制:某些特定的应用场景,例如插件化架构,可能需要在运行时加载新的插件或模块。在这种情况下,程序会根据需要动态加载新的类或模块。

六、总结

说句实话,线上确实弄了热部署,但是当时是没有热更新过,所以不是这种情况,其它两种场景也是没有的。

反正最后这个鬼还是没有查到更本原因。

为了避免再出现这个问题,静态代码块中的代码被我放到了方法中,然后使用调用来触发;希望未来某一天灵感乍现,使这个问题闭环。

希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。

同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。

感谢您的支持和理解!

相关推荐
想躺平的咸鱼干1 分钟前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying29 分钟前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·37 分钟前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
martinzh2 小时前
Spring AI 项目介绍
后端
Bug退退退1232 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠2 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
前端付豪2 小时前
20、用 Python + API 打造终端天气预报工具(支持城市查询、天气图标、美化输出🧊
后端·python
爱学习的小学渣2 小时前
关系型数据库
后端
武子康2 小时前
大数据-33 HBase 整体架构 HMaster HRegion
大数据·后端·hbase
前端付豪2 小时前
19、用 Python + OpenAI 构建一个命令行 AI 问答助手
后端·python