简单例子演示如何进行类的热加载(Hot Deployment)

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

标准 Java 启动器的类加载器层次

1. 引导类加载器(bootstrap):   加载内核 API,如 rt.jar(java.lang、java.io 等)
2. 扩展类加载器(extension):   加载的默认扩展来自于 jre/lib/ext
3. 系统类加载器(system):       类路径上的类,如 com.unmi.*

说明:这只是标准 Java 启动器运行程序时的类加载器层次,像应用服务器中的类加载器通常会多一两层,也是在这个基础上的延伸。上面的类加载层次存在自上而下的委托关系,委托加载不在这里细讲。

类加载器的规则有三

1. 一致性规则:类加载器不能多次加载同一个类
2. 委托规则  :在加载一个类之前,类加载器总参考父类加载器
3. 可见性规则:类只能看到由其类加载器的委托加载的其他类,委托是类的加载器及其所有父类加载器的递归集。(这个规则可能不太好理解,要举个例子就很容易理解的,这里也不细说)

实际的例子演示热加载

1. 建立工程,编写代码

前面铺垫的应该够厚了,开始用个例子来说明感受类的热加载(又名热部署 Hot Deployment)。这个例子采用 Eclipse 来做,首先要建立两个普通的 Java 工程,分别是 TestHotDeployInf 和 TestHotDeployImpl。让 TestHotDeployImpl 依赖于 TestHotDeployInf 工程,即在 TestHotDeployImpl 的 Build Path 中,Projects 标签页里把 TestHotDeployInf 工程选进来,因为编译 TestHotDeployImpl 中的类要用到 TestHotDeployInf 中的类。

然后在工程式 TestHotDeployInf 中新建一个接口(Cat.java) 和一个类(Client.java),内容分别是:

Cat.java(Cat 接口类,也可以用抽象类,用来引用需热加载的实现类的实例)

Client.java(测试热加载的客户端类)

还要在 TestHotDeployImpl 中添加一个 Cat 的实现类 CatImpl

2. 进行测试

运行 TestHotDeployInf 中的 Client 程序,按照下图中的指令说明,可观察到热加载的过程:

HotDeployment.jpg

3. 几个问题

1) 为什么要在单独的工程里放置 CatImpl 类(重要)
      
        主要是为了编译成的 CatImpl 类对于 TestHotDeployInf 的系统加载类不可见,就是不能放在 TestHotDeployInf 的程序的 classpath 中。
        这个问题可以说大,本应该提高一个层次来说明它。前面提过标准 Java 启动器加载器层次中有三个加载器,而在上面的 Client.java 中,我们看到用了一个自定义的 cl = new URLClassLoader(externalURLs) 类加载器来加载 com.unmi.CatImpl。也就是标准的类加载器又多了一层,这里估且把它叫做应用程序加载器(AppClassloader)。

        根据委托规则,执行 Client 时,要加载 com.unmi.CatImpl 时会首先委托加载 Client 类本身的系统加载器加载。如果编译出的 CatImpl.class 放在 Cat.class 相同的位置,那么就由系统加载器来加载 com.unmi.CatImpl,自定义加载器 cl 是没机会了。所以必须放在外面让系统加载器看不到 com.unmi.CatImpl 类。

        再依据一致性规则,如果系统加载器能加载了 com.unmi.CatImpl 类,以后你怎么修改 CatImpl 类,替换掉原来的类,内存中总是最先加载的那个 com.unmi.CatImpl 类版本。因为类只会加载一次。而用自定义的 cl 可不一样了,每次执行 cl.loadClass("com.unmi.CatImpl") 时都是用的一个新的 ClassLoader 实例,所以不受一致性规则的约束,每次都会加载最新版本的 CatImpl 类。

2) 关于类的卸载的问题

        上一条讲了加载 com.unmi.CatImpl 时,每次都 new 了一个新了 ClassLoader 实例,每次都加载最新的 CatImpl 类,那就引出了不再使用的 ClassLoader 实例和早先旧版本的 CatImpl 类实例的回收问题。在多数 JVM 中,它们如同普通的 Java 对象一样的处理,当它们无从触及时被当作垃圾被收集掉。也可能在某些 JVM 中这种情况对 ClassLoader 和旧版本 Class 实例的回收要特殊关照一下。

       这里的 Class 实例,就是对象调用 getClass() 得到的实例,如 CatImpl.getClass()。类实例和类加载器是相关联的,所有会出现这样的问题,相同类的静态变量可能表现为不同的值,因为它们可能是由不同的类加载器加载的。


对于 ClassLoader 确未细细深入,其实要展开的话内容也不多,关键就知道两点(还是回到了前面的两点,等于什么都没说哦):

      1)了解你的程序的类加载器层次,应该看看常见应用服务器(如 Tomcat) 的类加载器层次

      2) 理解类加载器的三个规则,着重理解委托机制

知道了类加载器层次,你就可以进行一些定制。如可以把一些包丢到 jre/lib/ext 中就能使用到;给 java 用参数 -Xbootclasspath 指定别的类或包就能替换掉 Java 核心 API 了。

对于可见性规则可以举两个例子:

      1) 对于标准的类加载器层次,放在 jre/lib/ext 中的类(由扩展类加载器加载)可以让放在 classpath 下的类(由系统类加载器加载) 访问到,反过来就不行了。

      2) 应用服务器中不同的 Web 应用中类不能相互访问,因为它们是由不同的类加载器加载的,且是在并行结构中。而在企业应用程序中的 WAR 包使用到 EJB 包和其他工具包,因为加载 WAR 包的类加载层是在加载 EJB 包和其他工具包的类加载器的下层。

本文链接 https://yanbin.blog/simple-java-hot-deployment/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

11 Comments
Inline Feedbacks
View all comments
sitinspring
15 years ago

先留个记号。

loocky
15 years ago

挺好,classloader再讲的详细点就更好了,支持!

lg
lg
15 years ago

classloader讲的深入点就好了

Yeats
Yeats
15 years ago

感謝..晚一點改寫一下我的code來試試看...

Yeats
Yeats
15 years ago

Hi 版主,

我已經改寫我的code,沒有問題可以正常運作了

而且上一次的討論,有關假設Job 如果還有reference 其他的class時,

其他class改的話也會重新抓新的...

ex. Job class 有new 一個B class

如果只改 B Class ,Job class 不改,因為ClassLoader 會重新讀一次

Job Class,而Job class 也會重新new 一次B Class,所以B Class也會抓最新的...

這就是我要的... 真是謝謝你囉..

隔叶黄莺
15 years ago

@Yeats

所以应该对这段程序进行一点优化,采用对 class 的缓存机制,就像是应用服务器对待 jsp 文件一样,如果 class 文件没有作修改的话,依然用缓存中的 class 数据,只在 class 有修改时才从文件中加载 class 数据,这样可以避免过多的 IO 操作,获得更好的程序性能。如果要重新加载的 class 很多的话更需要这种考虑。

Yeats
Yeats
15 years ago

版主,

關於提高效能減少IO的這件事情,我想過....最後我想到一個方式...

就是當我有修改class的時候,我才通知我的程式去用ClassLoader..否則

繼續使用緩存裡面的class...

至於怎嚜通知....

我有一各辦法...那就是使用環境的的attribute..

當我有更新的時候,我就同步在環境的attribute塞一個變數(這我有管理介面可以同步作..同時upload new class 和修改attribute)

然後我的scheduler再要執行job 前就先去看一下attribute 看需不需要重新load class...

我想這個辦法應該是可行的...不過我先澆這一個版本上去...晚一點再來做這一部份了....

Thanks!

隔叶黄莺
15 years ago

应该是可行的,像应用服务器判断是否要编译 JSP 文件就控制的比较精细了。

PDF阅读器下载
15 years ago

classloader不好懂,期待更加详细点……

hzy
hzy
15 years ago

如果CatImpl类的实现包括了其他的类(非系统类,自己写的),能把其他那些类也加载进去吗?谢谢!

隔叶黄莺
15 years ago

Of course, if the other classes(your own) need to hot deploy, you should make these invisible to the boot/extension/system classloader. In other words, they can't in the classpath.