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

六、总结

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

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

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

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

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

感谢您的支持和理解!

相关推荐
F-2H34 分钟前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
苹果酱056737 分钟前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
_oP_i2 小时前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx2 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康2 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘3 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意3 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
刘大辉在路上3 小时前
突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除
git·后端·gitlab·版本管理·源代码管理
FF在路上4 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进4 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html