热加载:在不停止程序运行的情况下,对类(对象)的动态替换。

Java ClassLoader 简述

Java中的类从被加载到内存中到卸载出内存为止,一共经历了七个阶段:加载、验证、准备、解析、初始化、使用、卸载。


接下来我们重点讲解加载和初始化这两步

加载

在加载的阶段,虚拟机需要完成以下三件事:

  • 通过一个类的全限定名来获取定义此类的二进制字节流

  • 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构

  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

这三步都是通过类加载器来实现的。而官方定义的Java类加载器有BootstrapClassLoaderExtClassLoaderAppClassLoader。这三个类加载器分别负责加载不同路径的类的加载。并形成一个父子结构。


类加载器名称 负责加载目录
BootstrapClassLoader 处于类加载器层次结构的最高层,负责 sun.boot.class.path 路径下类的加载,默认为 jre/lib 目录下的核心 API 或 -Xbootclasspath 选项指定的 jar 包
ExtClassLoader 加载路径为 java.ext.dirs,默认为 jre/lib/ext 目录或者 -Djava.ext.dirs 指定目录下的 jar 包加载
AppClassLoader 加载路径为 java.class.path,默认为环境变量 CLASSPATH 中设定的值。也可以通过 -classpath 选型进行指定

默认情况下,例如我们使用关键字new或者Class.forName都是通过AppClassLoader类加载器来加载的

正因为是此父子结构,所以默认情况下如果要加载一个类,会优先将此类交给其父类进行加载(直到顶层的BootstrapClassLoader也没有),如果父类都没有,那么才会将此类交给子类加载。这就是类加载器的双亲委派规则。

初始化

当我们要使用一个类的执行方法或者属性时,类必须是加载到内存中并且完成初始化的。那么类是什么时候被初始化的呢?有以下几种情况

  • 使用new关键字实例化对象的时候、读取或者设置一个类的静态字段、以及调用一个类的静态方法。

  • 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有进行初始化,那么先进行初始化。

  • 初始化一个类的时候,如果发现其父类没有进行初始化,则先触发父类的初始化。

  • 当虚拟机启动时,用户需要制定一个执行的主类(包含main()方法的那个类)虚拟机会先初始化这个主类。

如何实现热加载?

在上面我们知道了在默认情况下,类加载器是遵循双亲委派规则的。所以我们要实现热加载,那么我们需要加载的那些类就不能交给系统加载器来完成。所以我们要自定义类加载器来写我们自己的规则。

实现自己的类加载器

要想实现自己的类加载器,只需要继承ClassLoader类即可。而我们要打破双亲委派规则,那么我们就必须要重写loadClass方法,因为默认情况下loadClass方法是遵循双亲委派的规则的。

public class CustomClassLoader extends ClassLoader{private static final String CLASS_FILE_SUFFIX = ".class";//AppClassLoader的父类加载器private ClassLoader extClassLoader;public CustomClassLoader(){ClassLoader j = String.class.getClassLoader();if (j == null) {j = getSystemClassLoader();while (j.getParent() != null) {j = j.getParent();}}this.extClassLoader = j ;}protected Class<?> loadClass(String name, boolean resolve){Class cls = null;cls = findLoadedClass(name);if (cls != null){return cls;}//获取ExtClassLoaderClassLoader extClassLoader = getExtClassLoader() ;//确保自定义的类不会覆盖Java的核心类try {cls = extClassLoader.loadClass(name);if (cls != null){return cls;}}catch (ClassNotFoundException e ){}cls = findClass(name);return cls;}@Overridepublic Class<?> findClass(String name) {byte[] bt = loadClassData(name);return defineClass(name, bt, 0, bt.length);}private byte[] loadClassData(String className) {// 读取Class文件呢InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+CLASS_FILE_SUFFIX);ByteArrayOutputStream byteSt = new ByteArrayOutputStream();// 写入byteStreamint len =0;try {while((len=is.read())!=-1){byteSt.write(len);}} catch (IOException e) {e.printStackTrace();}// 转换为数组return byteSt.toByteArray();}public ClassLoader getExtClassLoader(){return extClassLoader;}
}

为什么要先获取ExtClassLoader类加载器呢?其实这里是借鉴了Tomcat里面的设计,是为了避免我们自定义的类加载器覆盖了一些核心类。例如java.lang.Object

为什么是获取ExtClassLoader类加载器而不是获取AppClassLoader呢?这是因为如果我们获取了AppClassLoader进行加载,那么不还是双亲委派的规则了吗?

监控class文件

这里我们使用ScheduledThreadPoolExecutor来进行周期性的监控文件是否修改。在程序启动的时候记录文件的最后修改时间。随后周期性的查看文件的最后修改时间是否改动。如果改动了那么就重新生成类加载器进行替换。这样新的文件就被加载进内存中了。

首先我们建立一个需要监控的文件

public class Test {public void test(){System.out.println("Hello World! Version one");}
}

我们通过在程序运行时修改版本号,来动态的输出版本号。接下来我们建立周期性执行的任务类。

public class WatchDog implements Runnable{private Map<String,FileDefine> fileDefineMap;public WatchDog(Map<String,FileDefine> fileDefineMap){this.fileDefineMap = fileDefineMap;}@Overridepublic void run() {File file = new File(FileDefine.WATCH_PACKAGE);File[] files = file.listFiles();for (File watchFile : files){long newTime = watchFile.lastModified();FileDefine fileDefine = fileDefineMap.get(watchFile.getName());long oldTime = fileDefine.getLastDefine();//如果文件被修改了,那么重新生成累加载器加载新文件if (newTime!=oldTime){fileDefine.setLastDefine(newTime);loadMyClass();}}}public void loadMyClass(){try {CustomClassLoader customClassLoader = new CustomClassLoader();Class<?> cls = customClassLoader.loadClass("com.example.watchfile.Test",false);Object test = cls.newInstance();Method method = cls.getMethod("test");method.invoke(test);}catch (Exception e){System.out.println(e);}}
}

可以看到在上面的演示图中我们简单的实现了热加载的功能。

优化

在上面的方法调用中我们是使用了getMethod()方法来调用的。此时或许会有疑问,为什么不直接将newInstance()强转为Test类呢?

如果我们使用了强转的话,代码会变成这样Test test = (Test) cls.newInstance()。但是在运行的时候会抛ClassCastException异常。这是为什么呢?因为在Java中确定两个类是否相等,除了看他们两个类文件是否相同以外还会看他们的类加载器是否相同。所以即使是同一个类文件,如果是两个不同的类加载器来加载的,那么它们的类型就是不同的。

WatchDog类是由我们new出来的。所以默认是AppClassLoader来加载的。所以test变量的声明类型是WatchDog方法中的一个属性,所以也是由AppClassLoader来加载的。因此两个类不相同。

该如何解决呢?问题就出在了=号双方的类不一样,那么我们给它搞成一样不就行了吗?怎么搞?答案就是接口。默认情况下,如果我们实现了一个接口,那么此接口一般都是以子类的加载器为主的。意思就是如果没有特殊要求的话,例如A implements B 如果A的加载器是自定义的。那么B接口的加载器也是和子类是一样的。

所以我们要将接口的类加载器搞成是AppClassLoader来加载。所以自定义加载器中加入这一句

if ("com.example.watchfile.ITest".equals(name)){try {cls = getSystemClassLoader().loadClass(name);} catch (ClassNotFoundException e) {}return cls;
}

建立接口

public interface ITest {void test();
}

这样我们就能愉快的调用了。直接调用其方法。不会抛异常,因为=号双方的类是一样的。

CustomClassLoader customClassLoader = new CustomClassLoader();
Class<?> cls = customClassLoader.loadClass("com.example.watchfile.Test",false);
ITest test = (ITest) cls.newInstance();
test.test();

参考文章

  • www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/index.html

  • Java虚拟机

作者:不学无数的程序员

链接:www.jianshu.com/p/d8fa14802b7a

厉害了,自己手写一个Java热加载!相关推荐

  1. 原来热加载如此简单,手动写一个 Java 热加载吧

    1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环 ...

  2. java热加载_java--热加载

    1. 什么是热加载 热加载是指可以在不重启服务的情况下让更改的代码生效,热加载可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环 ...

  3. java热加载_java热加载

    应用服务器一般都支持热部署(Hot Deployment),更新代码时把新编译的确类 替换旧的就行,后面的程序就执行新类中的代码.这也是由各种应用服务器的独 有的类加载器层次实现的.那如何在我们的程序 ...

  4. java中的saturn_saturn java 热加载(一)

    背景: 每次启用都要重启executor,在此之前还要确保该executor下没有任务在执行,很麻烦 方案: 将不常更换的公共包放在executor classpath下,仍然由saturn加载 将常 ...

  5. 写一个可以使用kingfisher加载图片的轮播器PPTView

    前言:关于图片轮播的第三方资源已经很多了,其中大多的使用都是很简单方便的, 但是从oc到swift上面来, 就写一写基本的常用的"控件"当作熟悉吧, 如果在这个过程中能帮到一些正在 ...

  6. mnist手写数字数据集_mnist手写数据集(1. 加载与可视化)

    >>欢迎 点赞,留言,收藏加关注<< 1. 模型构建的步骤: 在构建AI模型时,一般有以下主要步骤:准备数据.数据预处理.划分数据集.配置模型.训练模型.评估优化.模型应用,如 ...

  7. JQuery 写一个圆形动画加载进度条

    drawProcess()这个方法主要是画圆的大小 上面所写到的 setInterval("addNum(90,100)",20);  分别指的是: 1.这里的三个参数分别是90: ...

  8. idea修改代码后没有重新编译_SpringBoot中实现代码修改之后的自动更新与热加载...

    在实际的开发过程中,我们经常修改代码之后,手动的重启项目,查看修改效果.那么有没有一种方式能够快速的.自动的帮我们将修改代码自动更新,避免手动重启,从而提高开发效率呢?是有的,对于这个功能很多人对功能 ...

  9. 利用SPRING管理热加载的GROOVY对象!

    作者:paleXu的技术博客 来源:https://palexu.github.io/posts/spring-dynamic-load-groovy-bean?hmsr=toutiao.io& ...

最新文章

  1. 【数字信号处理】傅里叶变换性质 ( 序列傅里叶变换共轭对称性质示例 | 证明 共轭对称序列 x_e(n) 的 傅里叶变换 是 原序列傅里叶变换 的实部 )
  2. SAP系统中的银行主数据FI12
  3. MyEclipse 中自动安插作者、注释日期等快捷键方法
  4. go errgroup 用法示例
  5. pytorch中的参数初始化方法
  6. 通过批处理文件启动Oracle服务
  7. 使用git新增分支以及初始化分支等等一些列操作实战
  8. RHEL5 基于虚拟用户验证的VSFTP服务器
  9. Eclipse-maven项目不将resources下的文件打到classpath下
  10. Spring Boot集成thymeleaf异步刷新页面
  11. 启动Oracle的图像化管理工具OEM报错
  12. 嵩天python测验_考试 嵩天老师 :测验3: Python语法程序与设计(第3周)
  13. 华三交换机升级的ipe文件_H3C S5830V2[S5820V2]系列以太网交换机 配置指导-Release 2108-6W101...
  14. 数据湖技术之Hudi 集成 Spark
  15. 中国最具影响力的10位画家:八大山人
  16. arduino(19 ):使用ESP32连接 PS3 蓝牙手柄,需要在windows 上先连接成功,然后在修改mac地址,才可以连接成功,但是目前正在测试中,需要在windows上配对成功。
  17. 计算机类核心期刊审稿与发表周期,一般核心期刊的发刊周期有多长
  18. 算法第四版- 3.1
  19. vue里使用quill
  20. python怎么取共轭_自用numpy语法的共轭转置

热门文章

  1. php 栈实现历史记录后退,栈:如何实现浏览器的前进和后退功能
  2. 3090显卡 torch.cuda.is_available()返回false的解决办法
  3. innodb_flush_method理解【转】
  4. 递归基础之N皇后问题
  5. 螺旋方阵问题【数组】
  6. WinForm C#全局错误捕捉处理【整理】
  7. Lync2013 升级错误总结8 Lync2013 日志总是提示进程 RtcHost(5724) 收到了一个无效的客户端证书...
  8. GB/T 17710-1999 PHP生成校验码
  9. 活动目录应用篇一:使用windows server 2008 backup备份AD是的账户权限问题
  10. postfixadmin 导入数据库出错 ERROR 1062 (23000) at line 29