一、背景
我写文章很少交代写作背景,这个文章之所以特别说明写作背景,因为它确实很特别。
- 故事最开始是线上机器人突然告警一个服务被重启了。。。
-
当时是星期天,线上机器没有发生过故障,所以我们在群里问,谁重启了该服务。
-
结果最后是没人重启。
-
担心线上机器被攻击或者存在安全漏洞,急急忙忙连上去看了下,最终发现服务稳定运行很久了,确实没有人重启。
那么问题来了,这个告警是怎么来的?
告警的代码如上,乍看这个代码,是没有毛病的。那么怎么会突然告警呢?于是我研究了下这个鬼玩意,希望得到答案。
二、反射中的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)
可以使用指定的类加载器加载类,并执行静态初始化。
三、反射中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
可以有不同的实现,如系统类加载器、扩展类加载器和自定义类加载器。每个加载器都有一个父加载器,当加载器无法找到类时,它会委托给父加载器进行加载。这种层级结构支持类加载器的继承和委派模型。
四、Class.forName和ClassLoader加载机制的差异
看完上面发现,Class.forName
和ClassLoader
虽然都与类的加载有关,但在实现和应用方面存在着明显的差异。了解它们的区别和特性,有助于开发人员更好地理解Java
反射机制,并在需要的时候选择适当的加载方式。
五、运行中的程序,什么情况可能重新加载类呢?
在运行中的程序中,以下情况可能触发重新加载类:
1、修改类文件:如果在程序运行过程中修改了类文件(例如在开发环境中进行了代码修改),并且存在一种机制能够检测到文件的变化,那么程序可能会重新加载该类。
2、热部署机制 :某些特定的开发框架或工具(如Spring Boot
的DevTools
)具有热部署机制,它们会监控类文件的变化,并在检测到变化时自动重新加载类,以便应用程序能够即时获得代码更改的效果。
3、动态更新机制:某些特定的应用场景,例如插件化架构,可能需要在运行时加载新的插件或模块。在这种情况下,程序会根据需要动态加载新的类或模块。
六、总结
说句实话,线上确实弄了热部署,但是当时是没有热更新过,所以不是这种情况,其它两种场景也是没有的。
反正最后这个鬼还是没有查到更本原因。
为了避免再出现这个问题,静态代码块中的代码被我放到了方法中,然后使用调用来触发;希望未来某一天灵感乍现,使这个问题闭环。
希望本文对您有所帮助。如果有任何错误或建议,请随时指正和提出。
同时,如果您觉得这篇文章有价值,请考虑点赞和收藏。这将激励我进一步改进和创作更多有用的内容。
感谢您的支持和理解!