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

简介: #目前的Hot Reload方案 目前一般是容器(Web Container/Framework)才有能力做到热加载。因为通过自定义的ClassLoader实例来管理(bean/page/controller/configuration),如果这些文件有变化,立即创建一个新的ClassLoader

目前的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())。整个流程就像阴阳鱼,生生不息。
    tomcat_seq
  • StandContext.start()方法会调用WebappLoader.start(),为当前context创建一个专用WebappClassLoader:也就说每次context的加载都会有一个专属的WebappClassLoader。原因在于JVM Classloader体系的限制:web容器即使能够部分突破双亲委托加载规则的限制来加载某些class文件(原因1),但是依然无法突破一个ClassLoader不能重复加载某个class的限制(原因2),如果要保证修改后的class文件被重新加载,则需要重新创建一个ClassLoader实例来加载所有的class文件和资源文件,同时当前context中已经由之前ClassLoader实例加载的资源必须丢弃,否则会出现ClassCastExeption异常。

class_reload

  • 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。
play_seq

Play1首先通过ECJ来编译java文件生成class文件。在DEV模式下,由ApplicationClassloader负责校验java文件和资源文件是否被修改过,然后决定是否重新加载class文件,流程如下:
play_act

在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/

目录
相关文章
|
7月前
|
监控 Oracle Java
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
《深入浅出Java虚拟机 — JVM原理与实战》带你攻克技术盲区,探索各大JVM虚拟机特色 —— JVM故障排除指南(先导篇)
127 0
|
6月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
108 1
|
2月前
|
安全 Java API
🌟探索Java宇宙:深入理解Java技术体系与JVM的奥秘
本文深入探讨了Java技术体系的全貌,从Java语言的概述到其优点,再到Java技术体系的构成,以及JVM的角色。旨在帮助Java开发者全面了解Java生态,提升对Java技术的认知,从而在编程实践中更好地发挥Java的优势。关键词:Java, JVM, 技术体系, 编程语言, 跨平台, 内存管理。
46 2
|
7月前
|
缓存 算法 安全
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(二)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
73 0
|
7月前
|
缓存 Java C#
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍(一)
【JVM故障问题排查心得】「Java技术体系方向」Java虚拟机内存优化之虚拟机参数调优原理介绍
165 0
|
5月前
|
监控 算法 Java
JVM调优---堆溢出,栈溢出的出现场景以及解决方案
【7月更文挑战第3天】堆溢出(Heap Overflow)和栈溢出(Stack Overflow)是两种常见的内存溢出问题,通常发生在内存管理不当或设计不合理的情况下
82 3
|
6月前
|
存储 算法 Java
技术笔记:JVM的垃圾回收机制总结(垃圾收集、回收算法、垃圾回收器)
技术笔记:JVM的垃圾回收机制总结(垃圾收集、回收算法、垃圾回收器)
62 1
|
6月前
|
存储 缓存 监控
深入JVM:解析OOM的三大场景,原因及实战解决方案
深入JVM:解析OOM的三大场景,原因及实战解决方案
|
5月前
|
监控 Java 调度
探索JVM性能调优,调优不仅是技术挑战,更是成长过程。
【7月更文挑战第1天】探索JVM性能调优:** 本文深入JVM内存模型,关注堆内存与方法区、栈的优化,通过调整-Xms, -Xmx及垃圾收集器参数减少GC频率。探讨了Serial到G1等垃圾收集器的选择策略,利用jstat、jmap等工具诊断性能瓶颈。实战案例中,通过问题定位、内存分析解决Full GC问题,强调开发者需理解JVM原理,运用工具在复杂场景下实现高效调优。调优不仅是技术挑战,更是成长过程。
48 0
|
6月前
|
存储 缓存 算法
详解JVM内存优化技术:压缩指针
详解JVM内存优化技术:压缩指针