目前的Hot Reload方案
目前一般是容器(Web Container/Framework)才有能力做到热加载。因为通过自定义的ClassLoader实例来管理(bean/page/controller/configuration),如果这些文件有变化,立即创建一个新的ClassLoader实例来加载新的资源文件。例如:tomcat/jetty/Resin/.../SEAM/Grails
1、Hot deploy
应该称之为:热部署。热部署并不神秘,最暴力的热部署是自动重启当前应用的JVM。常见的热部署指是在不影响当前JVM中其它应用的前提下只对需要重新部署的程序进行更新。
基本上目前所有的应用服务器都支持热部署。但是如果应用太大,热部署的耗时是按照分钟来计:重新初始化各种配置、预热缓存、数据校验,可能会出现内存泄露的问题:http://zeroturnaround.com/rebellabs/rjc201/。
以tomcat的hot deploy为例: class文件被修改过,那么Tomcat会先卸载这个应用(Context),然后重新加载这个应用,其中关键就在于自定义ClassLoader的使用方式。
-
首先$CATALINA_HOME/conf/context.xml 中配置:
<Context reloadable="true"> ... </Context>
- tomcat启动,加载当前context:StandContext.start(),context启动成功后,会由ContainerBase中启动一个名为ContainerBackgroundProcessor的线程来监控是否有资源文件被修改,如果被修改再调用StandContext.reload()方法(先stop(),然后start())。整个流程就像阴阳鱼,生生不息。
- StandContext.start()方法会调用WebappLoader.start(),为当前context创建一个专用WebappClassLoader:也就说每次context的加载都会有一个专属的WebappClassLoader。原因在于JVM Classloader体系的限制:web容器即使能够部分突破双亲委托加载规则的限制来加载某些class文件(原因1),但是依然无法突破一个ClassLoader不能重复加载某个class的限制(原因2),如果要保证修改后的class文件被重新加载,则需要重新创建一个ClassLoader实例来加载所有的class文件和资源文件,同时当前context中已经由之前ClassLoader实例加载的资源必须丢弃,否则会出现ClassCastExeption异常。
-
tomcat的WebappClassLoader重写了findLoadedClass0()、findClass()方法,主要是在更改的逻辑在于:自己优先加载class文件,然后根据情况决定是否由父类加载;自定义缓存机制来缓存自己已经加载的class资源。
- 原因1:ClassLoader.loadClass(...) 是ClassLoader的入口点。当一个类没有指明用什么加载器加载的时候,JVM默认采用AppClassLoader加载器加载没有加载过的class,调用的方法的入口就是loadClass(...)。如果一个class被自定义的ClassLoader加载,那么JVM也会调用这个自定义的ClassLoader.loadClass(...)方法来加载class内部引用的一些别的class文件。重载这个方法,能实现自定义加载class的方式,抛弃双亲委托机制,但是即使不采用双亲委托机制,比如java.lang包中的相关类还是不能自定义一个同名的类来代替,主要因为JVM解析、验证class的时候,会进行相关判断。
- 原因2: 系统自带的ClassLoader,默认加载程序的是AppClassLoader,ClassLoader加载一个class,最终调用的是defineClass(...)方法,这时候就在想是否可以重复调用defineClass(...)方法加载同一个类(或者修改过),最后发现调用多次的话会有相关错误:
java.lang.LinkageError
attempted duplicate class definition
所以一个class被一个ClassLoader实例加载过的话,就不能再被这个ClassLoader实例再次加载(这里的加载指的是,调用了defileClass(...)放方法,重新加载字节码、解析、验证)。而系统默认的AppClassLoader加载器,他们内部会缓存加载过的class,重新加载的话,就直接取缓存。所与对于热加载的话,只能重新创建一个ClassLoader,然后再去加载已经被加载过的class文件。
注:tomcat版本为6.0.x,http://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk
资源文件每次的修改都会造成web容器的hot deploy,如果应用比较大,整个过程会很耗时。原因1、原因2,转自:http://www.cnblogs.com/balaamwe/archive/2013/05/13/3076086.html
2、HotSwap
从JDK1.4提供的技术,运行开发人员在debug过程中能够立即重载修改后的class。所有的IDE都支持这个特性(Intellij IDEA,Eclipse,NetBeans)。如果debug应用,并且修改了某些class,jvm会立即载入修改后的class。同样,这个技术也有限制:只允许修改方法体,不允许增加新的class、不允许新增字段、不允许新增方法、不允许修改方法签名、不允许。。。
详情请参考:
java.lang.instrument.Instrumentation.redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException, UnmodifiableClassException;
Play1 framework基于这个方法和hot deploy方式也实现了自己的热加载机制。这2种方式组合在一起能够避免仅仅是方法体被修改后重新加载context的问题,减少重新加载的次数。
Play1的热加载整个流程比较简洁,核心在于Eclipse Java Compile(ECJ)和自定义的ApplicationClassloader。
Play1首先通过ECJ来编译java文件生成class文件。在DEV模式下,由ApplicationClassloader负责校验java文件和资源文件是否被修改过,然后决定是否重新加载class文件,流程如下:
在Play context重启的时候会重新创建一个新的ApplicationClassLoader实例来加载所有的资源文件、class文件。之前的ClassLoader实例及相关被加载的资源都被丢弃有jvm gc回收。
Play1通过Instrumentation.redefineClasses方法一定程度上减少class文件修改导致的context重新加载问题,但是在实际开发场景中意义不大:class文件和资源文件经常有较大的变化;热加载后类的静态属性不能初始化;不支持spring、ibatis等常见框架。。。
JRebel可以当做HotSwap的增强版本,允许修改class结构:新增方法、字段、构造器、注解、新增class、修改配置文件。
3、OSGi
OSGi是Java模块化运行容器。根据个人的理解,可以把OSGi当做一个独立的Runtime,里面暴露一些资源接口,通过不同的classloader,可以提供不同版本、但是packageName.className完全相同的资源。毕大师《OSGi原理与最佳实践》这本中介绍了OSGi标准的各个实现框架以及OSGi的资源管理、热加载的原理。
将应用及其依赖jar划分到不同的module中,每次可以只发布更新某些module。这种发布和普通web容器的hot deploy类似,只是把应用切分为bundle,粒度更细,由OSGi容器来装载、卸载目标bundle,优点在于每次更新的module的变动量小于更新整个应用。OSGi框架意味着复杂繁琐的ClassLoader结构和加载机制,热加载机制同样也是通过不同ClassLoader实例的来实现。。。
基于ClassLoader实例的方案总结
这种方案的有点在于实现简单。缺点在于因为Classloader转换,会导致一些因为ClassLoader实例变化带来的ClassCastException,只能对被容器维护的代码有效果,普通的应用就很麻烦了。所以为了解决这些问题,ZeroTurnaround推出了JRebel。
JRebel
JRebel号称在类加载过程中,从字节码层面上解决hot reload的问题。
JRebel how to work
在Classloader级别上整合到JVM上,JRebel并没有在自定义Classloader,它只是很暴力的修改了JVM中Classloader的一些方法体逻辑,通过asm和jdk instrumentation的机制把ClassLoader的部分方法(包括native方法)的逻辑重写,使之能够管理重载的class文件。
JRebel能够对应用中的任何class起作用,也不会导致任何和Classloader相关的问题。
当一个class需要被加载,JRebel会在classpath或者rebel.xml配置指定的路径中试图查找相应的class文件。如果找到class文件,JRebel通过agent机制instrument这个class,并且维护class和class文件的关联关系。当应用中已经加载的class对应的class文件的修改时间变动后,扩展的Classloader就会被触发来加载新的class(Classloader并不会主动加载,而是在每次使用这个class的时候,check timestamp决定是否要加载class文件)。
JRebel同样能够监控rebel.xml上配置的JARs中的class文件。
重新加载配置文件
仅仅重新加载Java class是不能满足开发需求的。应用程序是由代码和配置文件(XML、Properties、Annotation等)组成的。JRebel能够重新加载修改后的配置文件。
JRebel只使用了Instrumentation API(http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html)
Instrumentation API在JDK5中引入的,支持运行过程中有限制的修改Java class。杯具的是这个限制就是要求只能修改方法体(和HotSwap一样)。JRebel使用了instrumentation来处理classloader和一些基础类,但是在实际的reload过程中没有任何用处。
深入理解JRebel
???
404
!!!
很抱歉,这是个收费的产品,源码已经被混淆了,很难了解整个hot reload的调用流程。JRebel解决了class热加载的问题,但是带来了新的问题:没有源码、商用付费(当然,如果你使用河蟹版,就忽略这些新问题)
so,为了解决这个问题,Hotcode2应时而生
Hotcode2
《漫谈JVM热加载技术(二)---Hotcode2 reload机制》
参考
http://www.cnblogs.com/balaamwe/archive/2013/05/13/3076086.html
http://www.infoq.com/cn/articles/code-generation-with-osgi
https://www.ibm.com/developerworks/cn/java/j-lo-osgi/
http://www.ibm.com/developerworks/cn/java/j-lo-jse61/index.html
http://zeroturnaround.com/software/jrebel/learn/faq/
http://zeroturnaround.com/rebellabs/rjc201/
http://manuals.zeroturnaround.com/jrebel/standalone/config.html#maven-rebel-xml
http://zeroturnaround.com/rebellabs/how-my-new-friend-byte-buddy-enables-annotation-driven-java-runtime-code-generation/