Java应用程序运行时升级软件,无需重新启动的方式有两种,热部署和热加载。

热加载

热加载即在在运行时重新加载class,实现原理主要依赖java的类加载机制,是在运行时通过重新加载改变类信息,直接改变程序行为。在实现方式可以概括为在容器启动的时候起一条后台线程,定时的检测类文件的时间戳变化,如果类的时间戳变掉了,则将类重新载入。

 

生产环境中,由于热加载这种直接修改jvm中字节码的方式是难以监控的,不同于sql等执行可以记录日志,直接字节码的修改几乎无法记录代码逻辑的变化,对既有代码行为的影响难以控制,对于越注重安全的应用,热加载带来的风险越大,这好比给飞行中的飞机更换发动机。

基于这种不安全性,在实际生产环境中应用很少。使用热加载的应用有两种:

1、需要频繁部署的应用; 2、无法停止服务的应用

在生产中,并没有需要频繁部署的应用,即使是敏捷,再快也是一周一次的迭代,并且通过业务划分和模块化编程,部署的代价完全可以忽略不计,对于现有的应用,启动耗时再长,也并非长到无法忍受,如果真的这么长,那更应该考虑的是如何进行模块拆分,分布式部署了。

对于无法停止服务的应用,比如现在的云计算平台这样分布式应用,采用分批上线也可以满足需求,类似热部署方案应该是放在最后考虑的解决方案。

 

开发环境中,频繁启动应用却随处可见,热加载可以显著的提升工作效率,强烈推荐使用热加载方式。(jrebel插件方式)

 

热部署

热部署就是在服务器运行时重新部署项目。原理与类加载类似,但它是直接重新加载整个应用,这种方式会释放内存,比热加载更加干净彻底,但同时也更费时间。

热部署作为一个比较灵活的机制,在实际的生产上运用还是有,尤其在云计算中运用挺多。

 

体验hotswap

以上均是理论派,下面玩点真格的。

我们知道对于Java应用程序来说,热部署其实就是在运行时更新Java类文件。

java类的加载过程,即一个java类文件到虚拟机里的对象,要经过如下过程。

go 热加载fresh 热加载是什么意思_jrebel

首先通过java编译器,将java文件编译成class字节码,类加载器读取class字节码,再将类转化为实例,对实例newInstance就可以生成对象。类加载器ClassLoader功能,也就是将class字节码转换到类的实例。

在java应用中,所有的实例都是由类加载器,加载而来。在类加载器中,java类只能被加载一次,并且无法卸载。那如何实现热部署呢?我们可以直接自定义类加载器,并重写ClassLoader的findClass方法。将java类卸载,并且替换更新版本的java类。

因此想要实现热部署可以分以下三个步骤:

1、自定义ClassLoader

2、更新class类文件

3、创建新的ClassLoader去加载更新后的class类文件。

 

talk is cheap:

自定义ClassLoader


import java.io.IOException;
import java.io.InputStream;

/**
 * 自定义类加载器,并override findClass方法
 * *
 */
public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring (name.lastIndexOf (".") + 1) + ".class";
            InputStream is = this.getClass ( ).getResourceAsStream (fileName);
            byte[] b = new byte[is.available ( )];
            is.read (b);
            return defineClass (name, b, 0, b.length);
        } catch (IOException e) {
            throw new ClassNotFoundException (name);
        }
    }
}


 

更新class类文件

1. 准备你想替换的结果。


public class HelloWorld {

    public void say() {
        System.out.println ("Hello World V2");

    }
}


2. 编译一下,得到class文件,存好。(我make后给它换了个名字还放在原文件夹,防止待会编译后覆盖没了)

3. 修改HelloWorldJava 文件


public class HelloWorld {

    public void say() {
        System.out.println ("Hello World V1");

    }
}


4.编译,得到新的class文件,

go 热加载fresh 热加载是什么意思_jrebel_02

5.开始偷梁换柱


public class Hotswap {

    public static void main(String[] args) throws Exception {

        loadHelloWorld ( );
        // 回收资源,释放HelloWorld.class文件,使之可以被替换
        System.gc ( );
        Thread.sleep (1000);// 等待资源被回收
        File fileV2 = new File ("/资料/技术学习/并发编程/out/production/并发编程/com/test/classloader/classLoaderTest/HelloWorld2.class");
        File fileV1 = new File ("/资料/技术学习/并发编程/out/production/并发编程/com/test/classloader/classLoaderTest/HelloWorld.class");
        fileV1.delete ( ); //删除V1版本
        fileV2.renameTo (fileV1); //更新V2版本
        System.out.println ("Update success!");
        loadHelloWorld ( );
    }

    public static void loadHelloWorld() throws Exception {

        MyClassLoader myLoader = new MyClassLoader ( ); //自定义类加载器
        Class<?> class1 = myLoader
                .findClass ("com.test.classloader.classLoaderTest.HelloWorld");//类实例
        Object obj1 = class1.newInstance ( ); //生成新的对象
        Method method = class1.getMethod ("say");
        method.invoke (obj1); //执行方法say
        System.out.println (obj1.getClass ( )); //对象
        System.out.println (obj1.getClass ( ).getClassLoader ( )); //对象的类加载器
    }
}


6. 结果:

go 热加载fresh 热加载是什么意思_热加载_03

 

存疑:

JRebel 插件实现热部署时,运行时支持对新的类,final字段等的热部署,而且效率很高,厉害了。