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

六、总结

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

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

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

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

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

感谢您的支持和理解!

相关推荐
一颗花生米。22 分钟前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
问道飞鱼23 分钟前
Java基础-单例模式的实现
java·开发语言·单例模式
ok!ko4 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2401_857622664 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589364 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰5 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没6 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
编程、小哥哥6 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程7 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统