漫谈JVM热加载技术(一)---目前常见的解决方案

目前的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 中配置:
    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
      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/

时间: 2025-01-07 19:09:01

漫谈JVM热加载技术(一)---目前常见的解决方案的相关文章

漫谈JVM热加载技术(三)--- Hotcode2 Adapter

Hotcode2中各个Adapter介绍 Hotcode2会在JVM启动阶段和应用运行阶段接入class文件的装载,也就是前一篇文章所说的JVM Init阶段和Runtime阶段.Adapter按照使用场景也可以分为2类,一种用是在Init阶段,还有一种是用在Runtime阶段. 1 JVM Init阶段 Init阶段的Adapter是在AgentMain.redefineJdkClasses 方法中重新定义各种JVM Class的字节码. 1.1 ClassLoaderAdapter Clas

类的热加载(Hot Deployment)的简单例子

应用服务器一般都支持热部署(Hot Deployment),更新代码时把新编译的确类 替换旧的就行,后面的程序就执行新类中的代码.这也是由各种应用服务器的独 有的类加载器层次实现的.那如何在我们的程序中也实现这种热加载功能呢?即 要在虚拟机不关闭的情况下(比如一个),换个类,JVM 就知道加载这个新类,执 行新类中的逻辑呢?下面就简单演示这样一个热加载的例子,首先大致了解一下 类加载器. 标准 Java 启动器的类加载器层次 1. 引导类加载器(bootstrap): 加载内核 API,如 rt

嵌入式系统中的模块动态加载技术

摘要 提出一种适用于嵌入式系统的模块动态加载技术,设计实现简单,占用资源少,开销小,并且成功运用于DeltaOS.可提高系统的灵活性和扩属性.介招加载与动态链接的原理和应用情况,解释相关术语,描述基本设计思路:详细说明该技术的核心.即模块声明.调用库.两级重定位表,最后给出结论. 关键词 模块 动态加栽 嵌入式系统DeltaOS 引 言随着电子技术的飞速发展,嵌人式设备应用越来越广泛,复杂度也越来越高.这使得硬件和软件设计比例发生了很大变化,软件开发的比重越来越大.然而传统嵌入式开发过程中需要将

JavaScript中的对象动态加载技术

 什么是JavaScript对象动态加载 JavaScript动态加载(JavaScript Object Dynamic Loading) - 之所以叫做动态,是应为其有别与通常的静态加载形式. 典型的JavaScript静态加载方式,是通过<script>标签将我们可能需要的所有JS文件依次嵌入到一个HTML页面中,当浏览器执行到<script> 标签,就会到我们指定的地方去加载JavaScript并运行,这时,文件中定义的无论方法.类.对象等,已经存在与浏览器,等待被使用.除

异步加载技术实现当滚动条到最底部的瀑布流效果_php技巧

异步加载技术实现瀑布流效果.当滚动条到最底部的时候触发一个事件,这个事件写入$.get()事件,向内部程序页传递类别id和页码,程序将会返回那个类别下的相对页的产品列表,如果程序查询当前类无产品即返回空. 滚动条事件要写在window.onscroll中才有效判断.如下: window.onscroll=function(){<br> // var scrolltop=document.documentElement.scrollTop||document.body.scrollTop; va

Android实现基于滑动的SQLite数据分页加载技术(附demo源码下载)_Android

本文实例讲述了Android实现基于滑动的SQLite数据分页加载技术.分享给大家供大家参考,具体如下: main.xml如下: <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/action_settings" android:orderInCategory="100" android:showAs

关于javascript模块加载技术的一些思考_javascript技巧

前不久有个网友问我在前端使用requireJs和seajs的问题,我当时问他你们公司以前有没有自己编写的javascript库,或者javascript框架,他的回答是什么都没有,他只是听说像requirejs和seajs是新东西新技术,很有价值所以想用它. 这位网友的问题引起了我对javascript模块加载技术的思考,上篇文章我给出了自己写的一个javascript库的基本结构,其实写这篇文章的一个起因就是因为我想使用requirejs或者seajs这样的技术来重新设计我写javascrip

再谈javascript图片预加载技术(详细演示)_javascript技巧

而本文所提到的预加载技术主要是让javascript快速获取图片头部数据的尺寸. 一段典型的使用预加载获取图片大小的例子: 复制代码 代码如下: var imgLoad = function (url, callback) { var img = new Image(); img.src = url; if (img.complete) { callback(img.width, img.height); } else { img.onload = function () { callback(

tomcat 热部署热加载

不重启Tomcat有两种方式:热部署.热加载     1.热部署:容器状况在运行时重新部署整个项目.这类环境下一般整个内存会清空,重新加载,这类方式  有可能会造成sessin丢失等环境.tomcat 6确实可以热部署了,而且对话也没丢.  2.热加载:最好是在调试过程中使用,免患上整个项目加载,Debug标准样式支持热加载.容器状况在运行时重 新加载转变编译后的类.在这类环境下内存不会清空,sessin不会丢失,但容易造成内存溢出,或者找不到方法.一般转变类的布局和模型就会有异常,在已经有的变