应用服务器一般都支持热部署(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 接口类,也可以用抽象类,用来引用需热加载的实现类的实 例)

package com.unmi;

/**

* Cat 接口,要热加载的类一定要有一个 接口或基类引用

* @author Unmi

*/

public interface Cat {

public void miaow();

}

package com.unmi;

/**

* Cat 接 口,要热加载的类一定要有一个接口或基类引用

* @author Unmi

*/

public interface Cat {

public void miaow();

}

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

package com.unmi;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.net.URL;

import java.net.URLClassLoader;

/**

* 测试热部 署 Hot Deployment 的客户端类

* @author Unmi

*/

public class Client {

private static ClassLoader cl;

private static Class catClass;

/**

* @param args

*/

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

BufferedReader br = new BufferedReader(new InputStreamReader (System.in));

Cat cat = createCat();

System.out.println("miaow, reload, or exit");

while(true) {

String cmd = br.readLine();

if (cmd.equalsIgnoreCase("exit")){

return;

} else if(cmd.equalsIgnoreCase("reload")){

reloadImpl();

cat = createCat();

System.out.println("CatImpl reloaded.");

} else if (cmd.equalsIgnoreCase("miaow")){

cat.miaow();

}

}

}

/**

* 使用加载 的类 Cat 类创建 Cat 实例

* @return Cat 实例

* @throws Exception

*/

public static synchronized Cat createCat() throws Exception{

if(catClass==null){

reloadImpl();

}

Cat newCat = (Cat) catClass.newInstance();

return newCat;

}

/**

* 用自定义的类加载器重新加载 ../TestHotDeployImpl/bin 目录中 的 CatImpl 实现类

* 注意这里的 ../TestHotDeployImpl/bin,方便 直接读取 TestHotDeployImpl 下随时

* 修改后编译成的新的 com.unmi.CatImpl 类,避免了 class 文件编译后拷贝到别处

* @throws Exception

*/

public static synchronized void reloadImpl() throws Exception{

URL[] externalURLs = new URL []{new URL("file:../TestHotDeployImpl/bin/")};

cl = new URLClassLoader(externalURLs);

catClass = cl.loadClass ("com.unmi.CatImpl");

}

}

package com.unmi;

import java.io.BufferedReader;

import java.io.InputStreamReader;

import java.net.URL;

import java.net.URLClassLoader;

/**

* 测试热部署 Hot Deployment 的客户端类

* @author Unmi

*/

public class Client {

private static ClassLoader cl;

private static Class catClass;

/**

* @param args

*/

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

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

package com.unmi;

/**

* Cat 的实现类,观察是否加载了最新代 码,可通过改变 miaow() 方法的输出

* @author Unmi

*/

public class CatImpl implements Cat {

@Override

public void miaow() {

System.out.println("I'm Hello Kity, I like play with you.");

//System.out.println("I'm Tom, Jerry always kids me.");

}

}

package com.unmi;

/**

* Cat 的实 现类,观察是否加载了最新代码,可通过改变 miaow() 方法的输出

* @author Unmi

*/

public class CatImpl implements Cat {

@Override

public void miaow() {

System.out.println("I'm Hello Kity, I like play with you.");

//System.out.println("I'm Tom, Jerry always kids me.");

}

}

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

Cat cat = createCat();

System.out.println("miaow, reload, or exit");

while(true){

String cmd = br.readLine();

if (cmd.equalsIgnoreCase("exit")){

return;

} else if (cmd.equalsIgnoreCase("reload")){

reloadImpl();

cat = createCat();

System.out.println("CatImpl reloaded.");

} else if(cmd.equalsIgnoreCase("miaow")){

cat.miaow();

}

}

}

/**

* 使用加载的类 Cat 类创建 Cat 实例

* @return Cat 实例

* @throws Exception

*/

public static synchronized Cat createCat() throws Exception{

if(catClass==null){

reloadImpl();

}

Cat newCat = (Cat)catClass.newInstance();

return newCat;

}

/**

* 用自定义的类加载器重新加载 ../TestHotDeployImpl/bin 目录中的 CatImpl 实现类

* 注意这里的 ../TestHotDeployImpl/bin,方便直接读取 TestHotDeployImpl 下随时

* 修改后编译成的新的 com.unmi.CatImpl 类,避免了 class 文件编译后拷贝到别 处

* @throws Exception

*/

public static synchronized void reloadImpl() throws Exception{

URL[] externalURLs = new URL[]{new URL("file:../TestHotDeployImpl/bin/")};

cl = new URLClassLoader (externalURLs);

catClass = cl.loadClass("com.unmi.CatImpl");

}

}

2. 进行测试

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

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 包和其他工具包的 类加载器的下层。

java热加载_java热加载相关推荐

  1. java中什么叫懒加载_java懒加载的原理

    聊一下以下名词.概念或用法:lazy.lazy="extra".inverse.fetch.fetch="join".fetch=" subselec ...

  2. java 静态初始化 调用_java JVM-类加载静态初始化块调用顺序

    测试类加载的全过程 public class Have { static { System.out.println("加载Have");//先加载Have再调用main方法 } p ...

  3. java 热部署实现_Java热部署的实现原理

    Java热部署的实现原理 Contributor:properties Type:简体中文 Date time:2017-01-22 00:10:19 Favorite:1 Score:0 返回上页 ...

  4. java时间加减_java时间加减

    展开全部 1.用java.util.Calender来实现 Calendar calendar=Calendar.getInstance(); calendar.setTime(new Date()) ...

  5. java 解析 svg文件_java – 如何加载和解析SVG文档

    概观 使用Apache Batik加载和解析SVG文件.该解决方案在将SVG文件转换为MetaPost的初步阶段显示Java代码.这应该提供有关如何使用Java从SVG文件加载,解析和提取内容的一般概 ...

  6. java ajax json 乱码_java+ajax加载中文json串后出现乱码问题的解决办法

    码农公社  210.net.cn  210是何含义?10月24日是程序员节,1024 =210.210既 210 之意. 一.问题描述 使用zTree异步刷新父级菜单时,服务器返回中文乱码.项目中使用 ...

  7. java 日期 年数_java 日期加减天数、月数、年数的计算方式

    因为某个项目需要统计 近1周.近1个月.近6个月 等数据,所以在时间的加减上面想了很多方式,最后决定用java.util.Calendar java.util.Calendar ,提供了计算时间的方式 ...

  8. java filter加时间戳_java filter加时间戳

    java filter加时间戳 [2021-01-31 18:31:52]  简介: java时间戳转php时间戳的方法:首先将java时间戳转成字符串,代码为[$utStr = $javaUt . ...

  9. java 时分秒加减_JAVA日期加减运算

    public static Date getBeforeMonth(Date date,int months) { Calendar calendar = Calendar.getInstance() ...

最新文章

  1. 牛!这位斯坦福PhD新生的论文被引数:接近4万
  2. 【控制】《多智能体系统的动力学分析与设计》徐光辉老师-第10章-带有分层领航者的多智能体系统的混杂协调
  3. HYSBZ - 1050(旅行comf 并查集Java实现)
  4. 揭秘IT人才特点:中美印日四国程序员比较
  5. 【转载保存】lucene正则查询使用注意
  6. 信息学奥赛一本通(2072:【例2.15】歌手大奖赛)
  7. 网络链路不稳定的排查问题方法
  8. Linux系统管理系列(1)——文件管理权限详解 chgrp chown chmod rwx等等
  9. 小米崔宝秋:一家互联网公司没有信息安全团队,就像在“裸奔”!
  10. nmea怎么转wgs84坐标c语言源码,NMEA-0813数据格式说明
  11. 2022 chrome离线下载包
  12. 肢解诺兰的逻辑:当科幻背离科学
  13. 测量string变量长度函数_C语言中测试字符串长度的函数
  14. Fabric.js中文文档
  15. ABAP 语法备忘 刘欣
  16. 16 位 CPU 寄存器英文全称
  17. 第十一章:如何拆分项目目标?
  18. Linux系列(五)、Vim编辑器的使用、账号用户组的管理、磁盘管理、进程管理
  19. Android PNG图片像素检测及剪裁优化
  20. 昨日互联网,明日区块链

热门文章

  1. Python+Selenium实现自动登录163邮箱导出邮件可按时间和内容进行筛选
  2. 解决security引入actuator后所有接口401问题
  3. 浅谈JavaScript中的apply,call和bind
  4. 二进制数据转换成十进制数
  5. 微服务.链路追踪概述和方案 (Cat Zipkin Skywaking,Sluth等组件对比选型)
  6. 【TOP100案例专访】当当网工程师林嘉琦谈双11大促经验及APM实践
  7. 药品进销存管理系统_药一点软件_连锁版介绍符合GSP最新要求
  8. 对称振子互阻抗曲线/查表[Matlab]
  9. java linux 管理系统_用Java开发一个本地服务管理软件
  10. 轻松Scrum之旅(上)