Java热更新

 
 

一、  解决方案

1)  自定义类加载器。

首先需要明白一点,class相等的判断条件不仅仅是类名相同,还需要加载它的ClassLoader相同。JVM内部规定一个ClassLoader不可以重复定义类,也就是说想要重定义一个类,就必须使用一个全新的ClassLoader。

JVM内部class被卸载的条件及其苛刻,甚至没有明确的方法可以直接调用,只有当加载该类型的类加载器实例为unreachable状态时,也就是没有任何实例,class才有可能被卸载。(启动类加载器实例永远为reachable状态,由启动类加载器加载的类型可能永远不会被卸载)

<span style="white-space:pre">	</span>public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
		Class<?> clazz = null;
		// 首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则继续。
		if (name.startsWith("com.wafer") || name.contains("Service")) {
			if (resolve) {
				resolveClass(clazz); // 链接指定的 Java 类
			}
			// 如果class类被修改过,则重新加载
			MoeLoader hcl = new MoeLoader(url);
			clazz = customLoad(name, hcl);
			return (clazz);
		}
		// 如果类的包名为"java."开始,则有系统默认加载器加载
		try {
			// 得到系统默认的加载cl
			ClassLoader system = ClassLoader.getSystemClassLoader();
			clazz = system.loadClass(name); // 加载名称为 name的类
			if (clazz != null) {
				if (resolve)
					resolveClass(clazz);
				return (clazz);
			}
		} catch (ClassNotFoundException e) {
			// Ignore
		}
		return customLoad(name, this);
	}


此范例的核心在于缓存自己已经加载的class,当再次需要加载时,如果发生变更,则可以new一个ClassLoader,这样新的字节码便可以即时生效。

JRbel是一种热更新的方案,它实现的方式是通过在启动参数中添加javaagent,即JVM底层提供的Instrumentation技术,来改变生成对象的方式。

2)  JVMTI虚拟机工具接口

JPDA是 Java 平台调试体系结构的缩写。通过 JPDA 提供的 API,开发人员可以方便灵活的搭建 Java 调试应用程序。 JPDA 主要由三个部分组成:Java 虚拟机工具接口(JVMTI)、Java 调试线协议(JDWP),以及 Java 调试接口(JDI)。参考资源(http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp?search_by=深入+Java+调试体系)

<span style="white-space:pre">	</span>List<Connector> connectors =    Bootstrap.virtualMachineManager().allConnectors();
		SocketAttachingConnector sac = null;
		for (Connector connector : connectors) {
			if (connector instanceof SocketAttachingConnector) {
				sac = (SocketAttachingConnector) connector;
			}
		}
		if (sac != null) {
			Map<String, Connector.Argument> defaultArguments = sac.defaultArguments();
			Connector.Argument hostArg = defaultArguments.get("hostname");
			Connector.Argument portArg = defaultArguments.get("port");
			hostArg.setValue("localhost");
			portArg.setValue("8787");
			VirtualMachine vm = sac.attach(defaultArguments);

			List<ReferenceType> rtList = vm.classesByName("com.wafer.demo.jdi.Foo");
			ReferenceType rt = rtList.get(0);
			Map<ReferenceType, byte[]> newByteCodeMap = new HashMap<ReferenceType, byte[]>(1);
			//获取特定类的字节码,发送到虚拟机
			byte[] newByteCode = genNewByteCodeUsingJavassist();
			newByteCodeMap.put(rt, newByteCode);

			if (vm.canRedefineClasses()) {
				vm.redefineClasses(newByteCodeMap);
			}
		}


Eclipse的热更新实现的方式便是如此,以Debug启动时,Eclipse会在JVM参数中添加-agentlib:jdwp=transport=dt_socket,suspend=y,address=localhost:63597,之后检测到代码变更时,将变更的字节码通过jdwp发送到应用。

BTrace可以发送字节码到应用,对拦截的类进行监控,其实现的方式同样用到JVMTI。使用VirtualMachine.attach(pid)连接到应用。

你可能感兴趣的:(eclipse,热更新,BTrace)