对于同一个类加载器实例,如果我们通过 ClassLoader.loadClass
或 Class.forName
多次去加载同一个全限定类名的类,那么我们得到的是该类对象的同一个实例,我们可以通过源码及单元测试来验证,先看看相关源码:
1 | /** |
可以看出 ClassLoader.loadClass
内部调用了 findLoadedClass(name)
进行了类是否已经加载的判断,对于相同 name
的 Class
实例,只会返回首次加载的 Class
实例。再看看 Class.forName
的源码:
1 | /** |
看看 Class.forName0
的原生实现 Class.c:
1 | JNIEXPORT jclass JNICALL |
其中 JVM_FindClassFromClassLoader
方法实现位于 jvm.cpp:
1 | JVM_ENTRY(jclass, JVM_FindClassFromClassLoader(JNIEnv* env, const char* name, |
继续跟踪源码至 systemDictionary.cpp:
1 | Klass* SystemDictionary::resolve_or_fail(Symbol* class_name, Handle class_loader, Handle protection_domain, bool throw_error, TRAPS) { |
在 resolve_instance_class_or_null
方法的入口处可以看到如果 Class
已经被加载,则直接返回。我们也可以写个简单的单元测试来验证:
1 | package me.tianshuang; |
运行后输出均为 true
。可以确认使用同一个类加载器实例去加载同名的类,只会对该类加载一次。
再看看 com.alibaba.compileflow.engine.process.preruntime.compiler.impl.FlowClassLoader 类加载器的实现:
1 | /* |
我们编写如下的单元测试尝试用不同的字节流对同一个 name 进行 defineClass
以尝试获取不同的 class 实例:
1 |
|
运行时直接抛出如下异常:
1 | java.lang.LinkageError: loader (instance of com/alibaba/compileflow/engine/process/preruntime/compiler/impl/FlowClassLoader): attempted duplicate class definition for name: "Test" |
可见 defineClass
不允许使用不同的字节流定义同名的类,底层具体的执行逻辑读者可以查看 OpenJDK 中相关源码,此处不再分析相关代码。
根据以上的逻辑,就不难理解在 compileflow 中重新加载类的实现,其采用重新创建一个类加载器去定义类的方式以实现类的重新加载,相关源码位于 AbstractProcessEngine#reload:
1 |
|
重新加载动态生成类的代码位于 CompilerImpl:
1 |
|
根据以上流程,就实现了运行时动态加载类的功能。
Reference
How to replace classes in a running application in java ?
Dynamically recompile and reload a class
Multiple loading of a class thru Class.forName() ??
Singleton Design Pattern in Java