JRebel 3.0于不久前发布,我们采访了Zeroturnaround的CTO Jevgeni Kabanov以详细了解JRebel的一些内部工作原理、适用性以及集成问题,并了解Java开发者能从这一技术中获得什么益处。
Jevgeni最近发表的一系列文章,详细讲解了JVM类加载和运行时类替换的相关内容。本文中的部分内容是从他这一系列文章中提取出来的,另一部分则是我们交流的内容。
开发过程中重新部署或重起Java应用都引入了不必要的时延,降低了生产效率。JRebel旨在通过给JVM增加一个处理运行时类替换的特殊javaagent启动参数来显著缩短开发周期。
开发周期与生产效率
为了对生产效率的相关讨论提供支持,Zeroturnaround收集了来自超过1100个Java开发者的统计数据。这些数据是公开的,用来计算平均小时等候时间。在平均小时等候时间中:有6分钟用来构建的;而大约有10分半是用来重新部署应用的。
除了直接等待时间外,还有其他隐性时间损耗。在软件开发这样的复杂任务环境中上下文切换也是个问题。有关开发流程的大量信息是保存在短期记忆中的,如果注意力被分散,要想恢复到原来的状态就得花大量的时间。诸如电话、email及个人需求等工作中断所导致的上下文切换成本在现有文献中已有所讨论。不过前面所说的由基础设施所引起的上下文切换也适用于此。
由于工作期间的成绩更少,低工作效率也会导致开发者积极性降低。
有几种方法可以做到在不重新启动应用的情况下更新类,但每种方法都有其问题:
JVM HotSwap
或许有人会说JVMHotswapping可以就地替换字节码,而且已经出现一段时间了(始于2002年,JDK 1.4)。但它有严格限制:它只能工作在Debug模式下,而且只能处理方法体内的变化。它不允许结构的变化或增/删类成员。Hotswap对classloader也是一无所知,它只是通过类名来识别类,并且只能处理已存在的类。
从Java5开始,hotswapping可通过instrumentationAPI来使用。JRebel也使用了该API,但仅仅用于底层类及类加载器,并没有在实际的重加载处理中运用。
JVM的复杂性(尤其是垃圾回收、内存布局及Hotspot)使得提供一个通用的透明类字节码替换方案比较困难。
其中一个问题是对象实例的状态必须不受影响,否则相关实例必须全部替换,对他们的所有引用也都要更新(包括级联变化)
应用服务器重部署(Redeployment)/热部署(Hotdeployment)
大多数servlet容器、应用服务器和OSGi的重部署特性实现思路都类似。
用专用类加载器(classloader)加载一个应用服务器(或其部分),如果你删除了对所有已加载类以及classloader的引用,你就能彻底卸载该应用并用一个新的classloader重新加载它。但是通常情况下,从JVM中彻底删除对所有实例、类或classlodar的引用是不可能的。
这样的话老应用仍部分存在,只是不再使用罢了。于是类加载器泄漏(classloader leaks)就会被引入,导致在连续重部署后内存消耗增加。
热部署可以有效地重启应用。为了恢复应用的状态,这些状态必须提前被保存并被重新应用或加载到“新”的应用或模块中。
当前基于组件的框架如Grails或RIFE都自己负责处理组件状态。这就是为什么用新classloader重新装载的组件在这些框架中不存在问题的原因。新组件的状态在框架中已经存在。组件的颗粒性也可以让很小的bundle被瞬时重新加载。
不过这种方式重新加载类存在一个普遍问题,即类的新老版本可能同时存在,框架必须小心处理这一问题。
JRebel
JRebel原先叫JavaRebel,自2007就有,它采取了一种不同的方式。当一个类被仪表化的(instrumented)类加载器加载时,在线创建了一个间接产物(indirection),其能够在重新加载时保持所有实例的身份和状态。该间接产物基于“高级编译技术(类似抽象字节码)”,会产生一个主类和几个匿名类、以及一些启用JIT的支持类。JRebel让方法调用尽可能完整,以减少对性能的影响。基于同样的原因,它避免将JDK仪表化(instrumenting JDK)。为了给重加载类提供反射API支持,该API调用的结果也相应地被修改。
创建额外的类会导致多分配20%到40%的空间。按照Jevgeni的说法,较小的类及深层继承都会占用更多的内存。但是这是由内部实现引起的,你不应该依赖于它。
为了在一个打包的部署中重新加载类,JRebel允许开发者将打包文件结构映射(通过rebel.xml配置文件)回开发空间(development workplace),以便让修改过的类以及所有其他资源得以重新加载。
由于JRebel对重载/刷新资源进行了全面综合考虑,因此也能够更新各种框架和容器的配置及元信息。为了能给不同框架都提供恰当的刷新设施,JRebel提供了一个开源API用以开发JRebel plugins。现有可用开源plugin中包含支持以下框架的plugin:Guice、Spring、Tapestry4、Struts 2、Wicked、Stripes、WebObjects。这些都是随发行包一起发行的。
上述三种不同类重加载方法的区别在zeroturnaround网站上有相应的比较矩阵加以说明。
JRebel 3.0改进之处
JRebel 3.0 版于4月16日发布,包含了不少重要改进。
按照Jevgeni的说法,最后一个问题比较棘手。当给类添加方法时,字节码框架的调用句柄接所收到了他们预期之外的合成方法调用。因此我们用一个redefineClass()方法对JRebel API进行了扩展,该方法还允许重定义字节码代理。使用该API,可以重写部分代理实现以允许动态类更新。现在有两个plugins专门支持这些框架。
还有一个遗留问题是JDK动态代理,由于他们对代理接口的限制因此很少需要集成。
JRebel的目标是具备更多兼容性,以能够与任何基于Java的底层架构无缝集成。其中重要的一步是改进了与EJB的集成。
为了获得更好企业集成特性,Zeroturnaround还针对老技术(Java 1.4、老的应用服务器版本、EJB 1.x/2.x)和集中许可证管理提供了JRebelEnterprise Add-On。该附加组件是作为免费更新提供给当前所有用户的。
许可和支持
JRebel可在个人许可(USD 59)或商业许可(USD 149)下使用。你还可以免费试用30天并有30天的退款保证。
Zeroturnaround对开源社区也提供支持,为开源开发者和Scala开发者提供了免费许可。