动态加载和卸载Java类

在开发Java服务器应用时,我们最希望开发的应用能够支持热部署,即不需要重启系统就可以用新的应用替换旧的应用。
如果使用动态语言,这些功能比较容易实现。但Java是静态语言,是否也可实现动态热部署呢?

首先,我们要深入了解一下Java的类装载(Class Loading)机制,和垃圾回收(Garbage Collection)机制。其中class loading 将负责装载新的应用包;GC将负责卸载旧的应用包。
装载新应用包的方法比较简单,只需要定制一个ClassLoader,从指定路径装载.jar文件即可。
要卸载一个ClassLoader,则必须要同时卸载通过该ClassLoader 载入的类的所有实例, 简单将ClassLoader的引用置为null并希望GC回收的做法是无效的。然而,要想统计并记录所有该ClassLoader载入的类实例是不现实的。而且,应用的装载和卸载功能,是服务器Framework的一部分,而不是应用业务功能的一部分。因此,framework也无法知道业务应用中何时载入和创建了多少对象。

解决方法还是有的。因为GC回收Heap中那些unreachable的对象,追根溯源,所有对象的创建都是由活动线程中发起的, 即所谓的Root set of references. 因此一旦活动线程结束运行,则可以说,线程中所有对象的根引用不可用了,则由根应用创建的所有对象也变为unreachable.

因此可以做如下设计:
[img]http://oursleepless.iteye.com/upload/picture/pic/94103/b64fa3b4-4cbe-34c0-bd7f-f344c02a17b5.png[/img]

其中Server负责service的deploy和undeploy。
其中deploy的过程可简单描述为:创建一个daemon线程,设置其context classloader为Service Jar包的ClassLoader,起动该Daemon线程。
undeploy的过程可简单描述为:停止服务(service daemon thread),设置线程context classloader为null, 等带线程彻底结束后手动执行GC来回收对象和ClassLoader.

Service接口如下:

public interface Service {

public final static String ATTR_SERVICE_ID = "SERVICE-ID";
public final static String ATTR_SERVICE_CLASS = "SERVICE-CLASS";

public void install() throws ServiceException;

public void start() throws ServiceException;

public void stop() throws ServiceException;

public void uninstall() throws ServiceException;

public String getId();
}


服务管理器如下:

/**
*
* @author less
* Responsible for deploy and undeploy Services.
*/
public class ServiceManager {

private final static HashMap<String, ServiceThread> installedServices = new HashMap<String, ServiceThread>();

public final synchronized static String install(File jarService) throws Exception {
MyJarLoader loader = new MyJarLoader(jarService, MyJarLoader.class.getClassLoader());
Manifest manifest = loader.getManifest();
Attributes attrs = manifest.getAttributes("Service");
String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
if (installedServices.containsKey(svcId)) {
uninstall(svcId);
}
ServiceThread t = new ServiceThread();
t.setContextClassLoader(loader);
t.setDaemon(true);
t.start();
loader = null;
return svcId;
}

public final static void uninstall(File jarService) throws Exception {
MyJarLoader loader = new MyJarLoader(jarService, MyJarLoader.class.getClassLoader());
Manifest manifest = loader.getManifest();
Attributes attrs = manifest.getAttributes("Service");
String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
loader = null;
uninstall(svcId);
}

public final static void uninstall(String svcId) throws Exception {
ServiceThread t = installedServices.get(svcId);
if (t.getStatus() == ServiceThread.Status.RUNNING) {
t.stopService();
}
ServiceManager.getInstalledServices().remove(svcId);
t.destroyService();
t.setContextClassLoader(null);
while (t.isAlive()) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
}
t = null;
System.gc();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.gc();
}

static HashMap<String, ServiceThread> getInstalledServices() {
return installedServices;
}

}

/**
*
* @author less
* A Customer Service carrier.
*/
class ServiceThread extends Thread {

public static enum Status {
RUNNING, STOPPED
}

private Status status = Status.STOPPED;
private Service service = null;

public Status getStatus() {
return this.status;
}

@Override
public void run() {
try {
Manifest manifest = ((MyJarLoader) getContextClassLoader()).getManifest();
Attributes attrs = manifest.getAttributes("Service");
String svcId = attrs.getValue(Service.ATTR_SERVICE_ID);
this.setName(svcId);
String svcClass = attrs.getValue(Service.ATTR_SERVICE_CLASS);
Class<Service> c = (Class<Service>) getContextClassLoader().loadClass(svcClass);
service = c.newInstance();
c = null;
service.install();
ServiceManager.getInstalledServices().put(svcId, this);
startService();
stopService();
} catch (Exception e) {
e.printStackTrace();
}
}

public void stopService() {
if (this.status != Status.STOPPED) {
try {
service.stop();
this.status = Status.STOPPED;
} catch (Exception e) {
e.printStackTrace();
this.status = Status.RUNNING;
}
}
}

public void startService() {
if (this.status == Status.STOPPED) {
this.status = Status.RUNNING;
try {
service.start();
} catch (Exception e) {
e.printStackTrace();
this.status = Status.STOPPED;
}
}
}

public void destroyService() {
try {
service.uninstall();
service = null;
} catch (Exception e) {
e.printStackTrace();
}
}
}

/**
*
* @author less
*
*/
class MyJarLoader extends JarLoader {

public MyJarLoader(File file, ClassLoader parent) throws Exception {
super(file, parent);
System.out.println(this + " is created.");
}

@Override
protected void finalize() throws Throwable {
destroy();
System.out.println(this + " is finalized.");
super.finalize();
}
}



示例服务代码,测试修改编译后重新部署:

/**
* @author less
*
*/
public class ExampleService1 implements Service {
private boolean bRun = true;

@Override
public void install() throws ServiceException {
System.out.println("== " + this + " is installed.");
}

@Override
public void start() throws ServiceException {
System.out.println("== " + this + " is started.");

//int i = 10000;
int i=0;
while (bRun) {
//System.out.println(this + " " + i--);
System.out.println(this + " " + i++);
try {
Thread.sleep(5000);
} catch (InterruptedException ie) {
}
}
System.out.println("== " + this + " is stopped.");
}

@Override
public void stop() throws ServiceException {
System.out.println("== Trying to stop " + this);
bRun = false;
Thread.currentThread().interrupt();
}

@Override
public void uninstall() throws ServiceException {
System.out.println("== " + this + " is uninstalled.");
}

@Override
public String getId() {
// TODO Auto-generated method stub
return null;
}

@Override
protected void finalize() throws Throwable {
System.out.println(this + " - is finalized.");
super.finalize();
}
}


控制台输出如下:
Server started.
org.lucky.server.MyJarLoader@27b15692 is created.
== org.lucky.service.example.ExampleService1@4e76fba0 is installed.
== org.lucky.service.example.ExampleService1@4e76fba0 is started.
org.lucky.service.example.ExampleService1@4e76fba0 10000
org.lucky.service.example.ExampleService1@4e76fba0 9999
org.lucky.service.example.ExampleService1@4e76fba0 9998
org.lucky.service.example.ExampleService1@4e76fba0 9997
org.lucky.server.MyJarLoader@2f833eca is created.
== Trying to stop org.lucky.service.example.ExampleService1@4e76fba0
== org.lucky.service.example.ExampleService1@4e76fba0 is uninstalled.
== org.lucky.service.example.ExampleService1@4e76fba0 is stopped.
org.lucky.service.example.ExampleService1@4e76fba0 - is finalized.
org.lucky.server.MyJarLoader@27b15692 is finalized.
== org.lucky.service.example.ExampleService1@7b36a43c is installed.
== org.lucky.service.example.ExampleService1@7b36a43c is started.
org.lucky.service.example.ExampleService1@7b36a43c 0
org.lucky.service.example.ExampleService1@7b36a43c 1
org.lucky.service.example.ExampleService1@7b36a43c 2
org.lucky.service.example.ExampleService1@7b36a43c 3

由上述测试可见,通过这种方式,可以实现类动态加载和卸载以及热部署。

这个方法为Java类的动态加载/卸载提供了一个思路。由于它需要为每一个需要部署的服务起动一个线程,虽然该线程只负责装载和卸载服务,服务运行时并不消耗CPU,但会固定占用一定大小的内存。测试值为每线程约占用350k内存。因此服务多时,存在StackOverflowError的风险。

在JDK7中据说提供了anonymous classloading 机制来支持动态类加载/卸载。需要使用的同学可以关注一下。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于unsafe加载卸载,需要先了解一些基本概念。 Java中,加载分为三个阶段:加载、链接、初始化。其中,链接阶段又分为验证、准备、解析三个阶段。 - 加载:查找并加载的二进制数据。 - 链接: - 验证:验证被加载的二进制数据的正确性。 - 准备:为变量(static修饰的变量)分配内存并初始化为默认值。 - 解析:把中的符号引用转换为直接引用。 - 初始化:为的静态变量赋值,执行静态初始化块。 Java中,卸载因为JVM设计的限制,是不可控的。只有当一个的所有实例都被回收,并且它的ClassLoader被回收时,才会尝试卸载。即一个ClassLoader卸载时,才会尝试卸载这个。 下面,我们将结合Unsafe来进行加载卸载操作。 1. 使用Unsafe加载 使用Unsafe,可以通过内存地址手动加载。下面是一个Unsafe加载的例子,在该例子中,首先使用URLClassLoader从指定路径加载TestClass.class,然后获取TestClass.class在内存中的地址,通过Unsafe手动加载TestClass.class。 ```java // 加载TestClass.class URL url = file.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[]{url}, null); Class<?> clazz = classLoader.loadClass("TestClass"); // 获取TestClass在内存中的地址 Field classField = Unsafe.class.getDeclaredField("theUnsafe"); classField.setAccessible(true); Unsafe unsafe = (Unsafe) classField.get(null); long classAddress = unsafe.staticFieldOffset(clazz.getDeclaredField("class$0")); // 使用Unsafe加载TestClass.class Class<?> loadedClass = (Class<?>) unsafe.getObject(null, classAddress); ``` 2. 使用Unsafe卸载 由于Java中对卸载的限制,使用Unsafe卸载是一件非常危险的操作,需要谨慎使用。下面是一个使用Unsafe卸载的例子,在该例子中,首先使用Unsafe手动加载TestClass.class,然后使用Unsafe卸载TestClass。 ```java // 使用Unsafe加载TestClass.class byte[] classData = getClassData(); Class<?> loadedClass = unsafe.defineClass(null, classData, 0, classData.length, null, null); // 使用Unsafe卸载TestClass.class Field classLoaderField = ClassLoader.class.getDeclaredField("classes"); classLoaderField.setAccessible(true); Vector<Class<?>> classes = (Vector<Class<?>>) classLoaderField.get(classLoader); classes.remove(loadedClass); Field classLoaderValueField = ClassLoader.class.getDeclaredField("classLoaderValues"); classLoaderValueField.setAccessible(true); Hashtable<Class<?>, Object> classLoaderValues = (Hashtable<Class<?>, Object>) classLoaderValueField.get(classLoader); classLoaderValues.remove(loadedClass); Class<?> classLoaderClass = Class.forName("java.lang.ClassLoader"); Method unregisterMethod = classLoaderClass.getDeclaredMethod("unregister", Class.class); unregisterMethod.setAccessible(true); unregisterMethod.invoke(classLoader, loadedClass); ``` 总的来说,在Java中手动加载卸载是一件非常危险的操作,需要谨慎使用,除非是在极端情况下才需要考虑使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值