# 前言

在之前的 深入浅出 JVM ClassLoader 一文中,我们说可以通过修改默认的类加载器实现热部署,但在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作。对于某些大型的应用来说,每次的重启都需要花费大量的时间成本,所以,如果能像我们之前说的那样,在不重启虚拟机的情况下更新一个类,在某些业务场景下变得十分重要。比如很多脚本语言就支持热替换,例如 PHP,只要替换了PHP 源文件,这种改动就会立即生效,且无需重启服务器。

今天我们就来一个简单的热部署,注意:不要小看他,这也是 JSP 支持修改的实现方式。

# 1. 怎么实现?

在上篇文章中,我们贴了一幅图:

我们知道,一个类加载器只能加载一个同名类,在Java默认的类加载器层面作了判断,如果已经有了该类,则不再重复加载,如果强行绕过判断并使用自定义类加载器重复加载(比如调用 defineClass 方法),JVM 将会抛出 LinkageError:attempted duplicate class definition for name。

但请注意,我们说同一个类加载器不可以加载两个同名的类,但不同的类加载器是可以加载同名的类的,加载完成之后,这两个类虽然同名,但不是同一个 Class 对象,无法进行转换。

那么我们是否可以利用这个特性,实现热部署呢?如同上图的步骤:使用自定义的类加载器,加载一个类,当需要进行替换类的时候,我们就丢弃之前的类加载器和类,使用新的类加载器去加载新的 Class 文件,然后运行新对象的方法。

让我们按照这个思路写段代码试试吧!


class AccountMain { public static void main(String[] args)
throws ClassNotFoundException, InterruptedException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { while (true) {
ClassLoader loader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
return super.loadClass(name);
} byte[] b = new byte[is.available()]; is.read(b);
return defineClass(name, b, 0, b.length); } catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException(name);
}
}
}; Class clazz = loader.loadClass("cn.think.in.java.clazz.loader.asm.Account");
Object account = clazz.newInstance();
account.getClass().getMethod("operation", new Class[]{}).invoke(account);
Thread.sleep(20000);
}
} }

上面这个类是一个 mian 方法类,该方法是一个间隔 20 秒的死循环,步骤如下:

  1. 创建一个自定义的 ClassLoader 对象,加载类的步骤不遵守双亲委派模型,而是直接加载。
  2. 使用刚刚创建的类加载器加载指定的类。
  3. 得到刚刚的Class 对象,使用反射创建对象,并调用对象的 operation 方法。

为什么间隔20秒呢?因为我们要在启动之后,修改类,并重新编译。因此需要20秒时间。

再看看 Account 类:

public class Account {
public void operation() {
System.out.println("operation...");
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}

该类很简单,只有一个方法,就是打印 operation...字符串。

我们还需要一个类,干什么用呢?我们刚刚说,需要修改 Account 类,然后重新编译,为了方便,我们创建一个类,专门用于执行修改后的 Account 类,因为执行后肯定重新编译了,省的我们去命令行使用 javac 了。

代码如下:

class ReCompileAccount {

  public static void main(String[] args) {
new Account().operation();
}
}

如何测试呢?

  1. 启动AccountMain main 方法。会立刻打印出 operation... 字符串,并开始等待20秒。
  2. 修改 Account 类的字符串为 operation.....new,
  3. 启动 ReCompileAccount 类,目的是重新编译 Accoutn类。
  4. 等待 AccountMain 类的打印。

不出意外的话,最后结果如下:

看到了吧,我们已经成功的把Accout 类修改了,并且是在不重启 JVM 的情况下,实现了热部署。就像我们刚刚说的,JSP 支持修改也是这么实现的,每一个 JSP 页面都对应着一个类加载器,当JSP 页面被修改了,就重新创建类加载器,然后使用新的类加载器加载 JSP (JSP 其实就是 Java 类)。

# 总结

基于 ClassLoader 的原理,我们实现了 Java 层面的热部署,但大家如果自己实现一遍的话,还是觉得很麻烦,诚然,JSP 使用这种方式没什么问题,因为他是自动编译的。但如果我们自己的应用的话,难道每次修改一个类,都要重新编译一遍,然后在给定的时间里去替换?我们能不能把这些手工活都交给 JVM 呢?实际上,Tocmat 也已经通过这种方式实现过了。限于篇幅,我们将在下一篇文章中讲述。

good luck!!!!!

探秘 Java 热部署的更多相关文章

  1. 探秘 Java 热部署三(Java agent agentmain)

    前言 让我们继续探秘 Java 热部署.在前文 探秘 Java 热部署二(Java agent premain)中,我们介绍了 Java agent premain.通过在main方法之前通过类似 A ...

  2. 探秘 Java 热部署二(Java agent premain)

    # 前言 在前文 探秘 Java 热部署 中,我们通过在死循环中重复加载 ClassLoader 和 Class 文件实现了热部署的功能,但我们也指出了缺点-----不够灵活.需要手动修改文件等操作. ...

  3. 揭秘Java热部署原理及JRebel(Hotcode)的实现原理

    基础知识:class卸载.热替换和Tomcat的热部署的分析HotSwap:HotSwap和JRebel原理成熟的热部署技术实现原理:深入探索 Java 热部署 java的热部署和热加载

  4. IntelliJ IDEA 的 Java 热部署插件 JRebel 安装及使用

    JRebel 介绍 JRebel for Intellij JRebel 在 Java Web 开发中, 一般更新了 Java 文件后要手动重启 Tomcat 服务器, 才能生效,  自从有了 JRe ...

  5. 深入探索 Java 热部署

    在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的应用来 ...

  6. 深入探索 Java 热部署--转

    在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.本文将探索如何在不破 ...

  7. Java 热部署深入探索

    简介 在 Java 开发领域,热部署一直是一个难以解决的问题,目前的 Java 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的 ...

  8. 探索 Java 热部署

    在 JAVA 开发领域,热部署一直是一个难以解决的问题,目前的 JAVA 虚拟机只能实现方法体的修改热部署,对于整个类的结构修改,仍然需要重启虚拟机,对类重新加载才能完成更新操作.对于某些大型的应用来 ...

  9. Java热部署相关

    今天发现早年在大象笔记中写的一篇笔记,之前放在ijavaboy上的,现在它已经访问不了了.前几天又有同事在讨论这个问题.这里拿来分享一下. 在web应用开发或者游戏服务器开发的过程中,我们时时刻刻都在 ...

随机推荐

  1. Python开发——4.集合和字符串拼接

    一.集合(set) 1.集合的特性: 不同元素组成.元素是无序排列的可hash值 2.集合转为列表 s1 = {11,"hechouzi",(11,22,33)} names = ...

  2. WPF中TreeView的+-号和连线style的一种实现

    最近又开始跟WPF打交道,项目里面用到了TreeView这个控件.然后需要有一个连线的外观就像是这样 二话不说,百度了一下,找到一个实现, 通道. 把代码拷贝到项目里面,跑了一下,看上去还不错.但是这 ...

  3. 关于javac和java

    1.为什么安装完jdk后不配置环境变量就能直接运行java,而不能运行javac 在安装jdk的时候jdk会自带一个jre(java运行环境),还会单独安装一个jre,默认路径是和jdk在同级目录,而 ...

  4. Java学习笔记30(集合框架四:List接口)

    List接口继承自Collection接口 具有重要的三大特点: 1.有序集合:存入和取出的顺序一致 2.此接口的用户可以对列表中每个元素插入位置精确的控制:可以通过索引操作 3.可以存储重复元素 L ...

  5. SpringCloud-Eurake坑

    首先遇到的问题 Eurake单台部署,很容易出现自我保护,发现web管理页面一大串红字警告 这个问题通过关闭自我保护机制可以简单解决..一般集群部署不会出现 eureka: server: renew ...

  6. 谈谈我们对userAgent的看法,为什么爬虫中需要userAgent?

    首先打开浏览器,按 F12 进入控制台(Console),然后输入:navigator.userAgent,即可看到 UA.例如: 1 2 Mozilla/5.0 (Windows NT 10.0; ...

  7. OC学习1——基本数据类型

    1.OC是在C语言的基础上进行扩展的一种面向对象的编程语言.很多基础知识都和C语言中的非常类似.首先介绍一下OC中的基本数据类型,整体框架如下图: 2.自动数据类型转换顺序:short --> ...

  8. Spark基础脚本入门实践1

    1.创建数据框架 Creating DataFrames val df = spark.read.json("file:///usr/local/spark/examples/src/mai ...

  9. python语法基础-初始化/继承

    写了一些程序,基本上都是直接def函数 然后在main方法中 调用 但是在一些应用程序中 会有基本语法的使用(初始化,继承) 初始化: 1.在程序执行时一定执行一次的操作 2.python中初始化in ...

  10. C# 判断字符串是否符合十六进制,八进制,二进制和十进制整数格式的正则表达式

    /// <summary> /// 判断是否十六进制格式字符串 /// </summary> /// <param name="str">< ...