3

开发、自测、联调期间代码可能会被频繁地修改,通常即使只增加了一行代码,都需要重启容器以检查执行效果。而热部署技术能够帮助开发人员减少重新部署的等待时间。本文的目的为调研热部署的技术现状及其对开发效率的帮助,并简单梳理其技术实现的难点。

clipboard.png

热部署技术

总结

JVM热部署目前有多种技术实现:官方、开源、商业。其中商业的JRebel功能强大,涵盖了日常开发中大部分热更新场景。以团队中一个基于Tomcat + Spring的业务后台为例,修改代码后,本地冷启动耗时4.5min,本地热部署的时间则小于1s,极大改善了开发效率。

官方实现

当前JVM和JVMTI(JVM Tool Interface)规范中通过相应的agent机制支持的retransformClass/redefineClass操作可以在加载前和加载后动态修改类的内容,从Java 5开始,这一功能还通过Instrumentation API直接提供给Java应用使用,但是其适用范围是受限的:只能修改已有方法的方法体。

以下摘自 JVM(TM) Tool Interface 1.2.3
The redefinition may change method bodies, the constant pool and attributes. The redefinition must not add, remove or rename fields or methods, change the signatures of methods, change modifiers, or change inheritance. These restrictions may be lifted in future versions. See the error return description below for information on error codes returned if an unsupported redefinition is attempted.

IDE的edit-and-continue功能(Intellij IdeaUpdate Application)就用到了这种被称为HotSwap的热部署技术,但是它的限制太大,完全无法满足实际开发中的需求。

Dynamic Code Evolution VM(DCEVM)

这是一个由JKU主导的、基于HotSpot VM的研究项目,诞生于2010年。该项目希望能动态修改类的任意元素,包括成员、方法、注解、继承等而无需重启JVM。目前的light版已经支持到Java 8 update 144, build 2

HotSwapAgent

基于DCEVM构建的开源项目,其完成度要高于DCEVM,目前已发布1.0版。对于常见的IDE、IoC/ORM/Log框架、J2EE应用容器的支持比较完善。根据官方文档,HA支持下列特性。

  • Add/remove/modify class fields.
  • Add/remove/modify methods. Add/remove/modify method annotations
  • Add/remove/modify classes including anonymous classes. HotswapAgent handless correct anonymous class redefinitions.
  • Add/remove static member of classes. HotswapAgent handles static member initialization.
  • Add/remove enum values
  • Refresh framework and application server settings

JRebel

Java世界中大名鼎鼎的热部署解决方案,热部署特性与上面提到的HotSwapAgent类似。当然作为一款商业软件,它支持的框架、IDE、J2EE应用容器的种类都更多,总计100+;同时支持Hotspot VM和Oracle VM;文档和社区支持非常完善,很容易上手。

最重要的是,没钱的码农可以通过赞助官方的Social Plan免费激活JRebel!

JRebel实测

测试环境为团队使用的Tomcat + Spring + SpringMvc
以下是实际开发中常见的改动类型的测试结果。【Pass】为支持,【Fail】为不支持。

  • 【Pass】在Spring和SpringMvc的配置Xml中增加Bean定义
  • 【Pass】新增Controller类,新增、修改Controller注解
  • 【Pass】新增、修改RequestHandler方法、方法体、方法签名、注解
  • 【Fail】在Spring和SpringMvc的配置Xml中增加、修改容器配置项。如<mvc:interceptors> / <mvc:cors> / <aop:aspectj-autoproxy> / <mvc:async-support>

浅谈热部署的实现难点

热部署的本质,简单的理解,是在运行中实时增加、替换JVM中的类文件而无需重启JVM。

众所周知,JVM使用ClassLoader加载类文件,内含的双亲委派模型通过指定类文件加载的顺序避免由于类冲突而导致核心类库加载失败。单个ClassLoader不能加载全限定名相同的类;不能修改已加载的类的声明;不能卸载已加载的类,除非移除整个ClassLoader,或者被GC回收。

那么,修改原有的类(如Test.class)的任意元素后,热部署就会面临很多问题。比方说:

  1. 两个全限定名相同的类如何加载?
  2. 类的实例化如何获取到新的类?
  3. 更新类的声明后,如增加类方法、实例方法;修改类方法、实例方法签名、方法体、方法注解;新增、修改类变量、实例变量;修改接口、类的继承关系,调用点怎么指向新的类?
  4. 如果JVM使用了内联优化技术呢?
  5. 如何保证反射正确,比如调用ClassgetName()getMethods()getField()等方法时如何获取到新的类?
  6. 如果用了容器或者框架,修改JavaConfig或者XML后,怎么反映到容器里?

热部署问题在底层绕不开ClassLoader,当一个类被更新后,需要被重新载入到ClassLoader中,原先对类变量、实例变量、类方法、实例方法的调用都需要重定向到新类。可以通过引入一个包含所有符号链接的中间层,当JVM加载用户的类时进行动态增强,并记录下涉及的符号链接。

举几个实现思路的小例子:

  1. 在类加载时为新的类起一个新的限定名(如原来的类名是Test,而新的类名是Test_v1),绕开ClassLoader的限制。
  2. 所有指向老的类的符号链接都实时替换为相应的新符号链接
  3. 在ClassLoader中查找老的类时返回新的类
  4. 监控容器配置文件,发生变化后调用容器refresh API

Reference

  1. Java SE 6 新特性 Instrumentation 新功能
  2. HotSwapAgent
  3. 深入探索 Java 热部署
  4. DCEVM
  5. Get True Hot Swap in Java with DCEVM and IntelliJ IDEA
  6. Features - JRebel
  7. HotSwap和JRebel原理
  8. 实现增强的java class hotswap (三) 解决方案 续
  9. 实现增强的java class hotswap (三) 解决方案
  10. Java是否可以做到修改类而不用重启JVM?

Jiadong
454 声望42 粉丝

秋名山撒欢,排水沟过弯