`
oursleepless
  • 浏览: 9539 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

动态加载和卸载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.

因此可以做如下设计:


其中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
1
分享到:
评论
2 楼 blackdtj 2013-04-11  
请问JarLoader是哪个包提供的类?
1 楼 deepnighttwo 2011-07-11  
想卸载service1的上海,如果别的service持有哪怕一个service1中创建的对象,那也是无法卸载成功的。

所以这个方法限制非常大,如果想卸载一个service,要求service之前只能通过中间数据格式(比如xml,json)来交换数据。或者通过每个service公共的parent class loader加载的类的实例来交换数据。

如果作为一个框架放开了用的话,一旦别的service持有了另一个service创建出来的对象,那就是灾难。

相关推荐

    动态编译、加载java类

    在Java编程中,动态编译和加载类是一种高级特性,它允许程序在运行时编译源代码并将其加载到Java虚拟机(JVM)中。这种能力对于开发灵活性高、可扩展性强的应用程序非常有用,比如在服务器端处理动态生成的代码、...

    动态加载类机制JAVA

    - OSGi是一个用于创建模块化Java应用的框架,它支持动态模块的安装、启动、更新和卸载,极大地利用了动态加载类的优势。 通过理解并熟练掌握这些知识点,开发者可以构建更加灵活、可维护的Java应用程序,尤其在...

    IBM AIX6.1环境下安装、卸载Java JRE、SDK

    本文旨在详细介绍如何在 IBM AIX 6.1 操作系统环境中进行 Java JRE 和 SDK 的安装及卸载,并对相关的环境变量配置方法进行了详尽的说明,帮助用户在该平台上顺利搭建起 Java 开发与运行环境。 #### 二、准备工作 ...

    从JVM分析Java的类的加载和卸载机制

    类加载涉及到类加载器的选择和类的生命周期,而类的卸载则依赖于Java的垃圾收集器和引用关系的判断。在实际编程中,开发者可以通过自定义类加载器来实现特定的加载逻辑,同时需要注意合理管理类的生命周期,以达到...

    从一个小例子来看动态卸载class

    在Java编程语言中,动态加载和卸载Class是高级特性之一,主要涉及到反射、类加载器和类的生命周期管理。这个小例子将帮助我们理解如何实现动态卸载Class,以及为何在Java中不能直接实现完全的类卸载。 首先,我们要...

    类加载器(java)

    类加载器的知识不仅仅局限于基础概念,还包括类加载的时机(静态加载、动态加载)、类加载器的实现(如自定义类加载器)、类的卸载、以及类加载器与安全策略的关系等。深入理解和掌握这些知识点,对于开发高效、安全...

    Java动态类加载机制应用研究.zip

    Java动态类加载机制是Java平台一个非常重要的特性,它允许程序在运行时动态地加载类,增强了软件的灵活性和可扩展性。动态类加载对于理解和优化Java应用程序的性能、实现插件系统、以及处理复杂的模块化系统至关重要...

    类加载器文件

    1. **卸载**: Java类一旦被加载到JVM中,通常不会被卸载。但在特定条件下,如果类加载器不可达,那么该加载器加载的类可能会被卸载。不过,这是JVM的内部行为,对外部是透明的。 2. **重载**: 由于Java类一旦加载...

    自定义Java类加载器

    自定义Java类加载器允许我们根据特定需求扩展默认的加载机制,例如,从非标准位置加载类或者实现动态加载。在Java中,类加载过程分为加载、验证、准备、解析和初始化五个阶段。 首先,让我们了解Java中的默认类加载...

    jira8.4.1的plugin加载卸载机制

    OSGi使得插件可以独立地进行加载、更新和卸载,而不会影响到其他组件或系统的稳定性。每个插件都是一个独立的OSGi模块,有自己的类加载器,这样可以确保不同插件之间的隔离性。 二、插件加载机制 1. **初始化**:...

    Java类重新加载101对象类和类加载器Java开发Jav

    在“Java类重新加载101对象类和类加载器”的主题中,开发者还需要理解类加载的生命周期、类的可见性以及类加载器的层次结构。这些都是深入学习Java动态性的重要部分,也是提升开发技能和解决问题的关键。通过深入...

    解决classloader的jar包

    包括commons-logging commons-beanutils commons-lang ezmorph json-lib-2.4-jdk15 commons-collections-3.2.1的jar包,可以解决 org/apache/commons/lang/exception/NestableRuntimeException的问题

    Java的类加载器

    类加载器的概念是Java动态加载和运行时类重定义的关键。下面将详细探讨类加载器的工作原理、层次结构以及自定义类加载器的应用。 1. **类加载的生命周期** 类加载的过程包括加载、验证、准备、解析和初始化五个...

    Android 下通过反射调用加载/卸载(mount/unmount) 外置SD卡

    这种情况下,可以通过反射调用来执行加载(mount)和卸载(unmount)操作。下面我们将详细探讨这一主题。 首先,我们需要理解Android的存储架构。Android系统将外部存储分为内部存储(Internal Storage)和外部存储...

    Java类加载器的详解

    这个过程是Java动态加载机制的核心,使得Java程序具有高度的可扩展性和灵活性。 类加载器分为以下几种类型: 1. **引导类加载器(Bootstrap ClassLoader)**:这是JVM自带的第一类加载器,负责加载JRE核心库,如rt...

    Java虚拟机----类的加载过程.docx

    Java虚拟机(JVM)的类加载过程是Java程序运行的基础,它涉及到类加载器、类的生命周期和一系列复杂的步骤。在这个过程中,类加载器主要任务是根据类的全限定名加载二进制字节流并转化为`java.lang.Class`对象。整个...

    Android动态加载(下)——加载已安装APK中的类和资源

    通过自定义ClassLoader和Resource,我们可以实现对已安装APK的类和资源的动态加载,从而为应用的扩展性和灵活性提供强大的支持。然而,这也意味着需要处理更多的兼容性问题和安全性挑战,开发者需要谨慎对待每一个...

    java类加载机制

    Java类的生命周期主要包括以下几个阶段:加载、验证、准备、解析、初始化、使用和卸载。其中,类的初始化时机尤为重要。根据Java规范,以下几种情况必须对类进行初始化: 1. **遇到特定字节码指令**: - 当遇到 `...

    java 动态编译打包 动态编译可以用于实现动态代码生成、动态加载、插件化等功能

    综上所述,Java动态编译打包是一种强大的技术,它使Java应用能够灵活地适应变化,实现动态代码生成、动态加载和插件化。通过理解和熟练运用这些技术,开发者可以创建出更具弹性和可维护性的软件系统。

    Java类加载机制 PDF 下载

    8. **动态加载**:Java允许通过反射机制动态加载类,这样可以提高程序的灵活性,实现插件化开发,使得在运行时根据需要加载特定的类。 9. **类加载器优化**:合理设计和使用自定义类加载器,可以实现类的热替换,...

Global site tag (gtag.js) - Google Analytics