最近在对以后平台进行优化,原先每次部署新模块,或者更新模块都需要重起进程,准备改成增加新模块自动部署,更新模块也能重新加载。
原先系统是在每次启动时从一张配置表里读取每个模块的类路径,然后通过反射机制实例化模块,之后就再不管那张配置表了。优化过程中,在系统增加了一个线程,专门用于检测配置表是否有模块更新需求或新增加的模块。如有的话则重新加载模块或新增模块。新增模块的工作很顺利,但在更新模块时却遇到一些问题。
刚开始时重新加载一个模块,我认为只要重新执行一次Class.forName(classpath),然后在newInstance()一下就万事大吉了,没想到重新执行一次Class.forName(classpath).newInstance()后的模块还是老的,压根就没有更新,试了多次依然如此。于是到网上google一下,查阅了相关资料,发现了一些问题:
在Java 1.2后,java class的加载采用所谓的委托模式(Delegation Modle),当调用一个ClassLoader.loadClass()加载一个类的时候,将遵循以下的步骤:
1)检查这个类是否已经被加载进来了?
2)如果还没有加载,调用父对象加载该类
3)如果父对象无法加载,调用本对象的findClass()取得这个类。
也就是说,如果这个类已经被加载了,JVM就不会在重新加载一遍。这个问题有点郁闷了。java有没有卸载类的方法呢:
当一个java class被加载到JVM之后,它有没有可能被卸载呢?我们知道Win32有FreeLibrary()函数,Posix有dlclose()函数可以被调用来卸载指定的动态连接库,但是Java并没有提供一个UnloadClass()的方法来卸载指定的类。在Java中,java class的卸载仅仅是一种对系统的优化,有助于减少应用对内存的占用。既然是一种优化方法,那么就完全是JVM自行决定如何实现,对Java开发人员来说是完全透明的。在什么时候一个java class/interface会被卸载呢?Sun公司的原话是这么说的:"class or interface may be unloaded if and only if its class loader is unreachable. Classes loaded by the bootstrap loader may not be unloaded."
卸载类的方法是没有的,不过我们有一些变通的方法可以实现卸载,动态反射创建的类是可以被JVM收回的,跟对象一样,只要打断所有指向该类的引用就ok。下面是从《深入java虚拟机第二版》一书中学得的方法:
############## 首先创建一个自己的类加载器 #####################
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/**
*
* @author dingwy
*
*/
public class MyClassLoader extends ClassLoader {
private String basepath;
public MyClassLoader(String basepath) {
this.basepath = basepath;
}
public MyClassLoader(ClassLoader parent, String basepath) {
super(parent);
this.basepath = basepath;
}
public Class findClass(String className) throws ClassNotFoundException {
byte classData[];
classData = getTypeFromBasePath(className);
if (classData == null) {
}
return defineClass(className, classData, 0, classData.length);
}
public byte[] getTypeFromBasePath(String typeName) {
FileInputStream fis = null;
String fileName = basepath + File.separatorChar + typeName.replace('.', File.separatorChar)
+ ".class";
try {
fis = new FileInputStream(fileName);
} catch (Exception e) {
e.printStackTrace();
return null;
}
BufferedInputStream bis = new BufferedInputStream(fis);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
int c = bis.read();
while (c != -1) {
out.write(c);
c = bis.read();
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return out.toByteArray();
}
}
############## 创建一个接口 #####################
public interface TestInterface {
public void test(); }
############## 扩展接口 #####################
public class TestA implements TestInterface {
public void test() {
System.out.println("1");
}
}
############### 测试 #######################
public class test {
public static void main(String[] args) {
while(true) {
try {
MyClassLoader myClassLoader = new MyClassLoader(
"D://");
Class c = myClassLoader
.findClass("TestA");
Object obj = c.newInstance();
Test t = (Test) obj;
System.out.print("1 ");
t.test();
myClassLoader = null;
c = null;
obj = null;
t = null;
Thread.sleep(2000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
注:在test类运行过程中,可以改变TestA 中输出的值观察输出结果,如果是使用Class.ForName()进行类加载的话,输出内容是不会变的,而使用自定义的加载器加载则可以。