厉害了,自己手写一个Java热加载!

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php相应的扩展的对应链接地址

window下memcached安装&#xff1a;http://code.jellycan.com/memcached/ memcached.exe -m 15 -p 11211开启服务转载于:https://www.cnblogs.com/jakentec/p/4496828.html

duration java_Java Duration类| toMinutes()方法与示例

duration javaDuration Class toMinutes()方法 (Duration Class toMinutes() method) toMinutes() method is available in java.time package. toMinutes()方法在java.time包中可用。 toMinutes() method is used to convert this Duration into the number of minutes. toMin…

Android 如何让EditText不自动获取焦点

文章转自&#xff1a;http://www.cnblogs.com/error404/archive/2012/12/28/2837294.html 在项目中&#xff0c;一进入一个页面, EditText默认就会自动获取焦点。那么如何取消这个默认行为呢&#xff1f;在网上找了好久&#xff0c;有点 监听软键盘事件&#xff0c;有点 调用 c…

公司新来的小可爱,竟然把内存搞崩了!

ThreadLocal使用不规范&#xff0c;师傅两行泪组内来了一个实习生&#xff0c;看这小伙子春光满面、精神抖擞、头发微少&#xff0c;我心头一喜&#xff1a;绝对是个潜力股。于是我找经理申请亲自来带他&#xff0c;为了帮助小伙子快速成长&#xff0c;我给他分了一个需求&…

理解Node.js的event loop

为什么80%的码农都做不了架构师&#xff1f;>>> 关于Node.js的第一个基本概念是I/O操作开销是巨大的&#xff1a; 所以&#xff0c;当前变成技术中最大的浪费来自于等待I/O操作的完成。有几种方法可以解决性能的影响&#xff1a; 同步方式&#xff1a;按次序一个…

硬核|定时任务的10种实现方案,满足你的不同需求!

最近有几个读者私信给我&#xff0c;问我他们的业务场景&#xff0c;要用什么样的定时任务。确实&#xff0c;在不用的业务场景下要用不同的定时任务&#xff0c;其实我们的选择还是挺多的。我今天给大家总结10种非常实用的定时任务&#xff0c;总有一种是适合你的。一. linux自…

字符串分割--java中String.split()用法

文章转自&#xff1a;http://yangzb.iteye.com/blog/1824761 在java.lang包中有String.split()方法&#xff0c;返回是一个数组。 1、 “.”和“|”都是转义字符&#xff0c;必须得加"\\"; 如果用“.”作为分隔的话&#xff0c;必须是如下写法&#xff1a; String.s…

duration java_Java Duration类| 带示例的dividBy()方法

duration java持续时间类divideBy()方法 (Duration Class dividedBy() method) dividedBy() method is available in java.time package. splitBy()方法在java.time包中可用。 dividedBy() method is used to divide this Duration by the given parameter (divisor) (i.e. thi…

IP子网划分

IP和子网掩码我们都知道&#xff0c;IP是由四段数字组成&#xff0c;在此&#xff0c;我们先来了解一下3类常用的IPA类IP段 0.0.0.0 到127.255.255.255 (0段和127段不使用)B类IP段 128.0.0.0 到191.255.255.255C类IP段 192.0.0.0 到223.255.255.255XP默认分配的子网掩码每段…

Semaphore自白:限流器用我就对了!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;大家好&#xff0c;我是 Semaphore&#xff0c;我的中文名字叫“信号量”&#xff0c;我来自 JUC 家族&#xff08;java.uti…

机房合作—我是组长

五一期间开始机房合作&#xff0c;到现在一个多星期了。我&#xff0c;蕾蕾&#xff0c;亮亮一组&#xff0c;我担任组长一职。在着手准备项目开始之前&#xff0c;我们听取了各位师父的一些建议&#xff0c;也算是给我们指明一下方向。第一天晚上&#xff0c;我召开了我们项目…

golang的new函数_new()和make()函数以及Golang中的示例

golang的new函数In Golang, to allocate memory, we have two built-in functions new() and make(). 在Golang中&#xff0c;要分配内存&#xff0c;我们有两个内置函数new()和make() 。 1)new()函数 (1) new() function) Memory returned by new() is zeroed. new()返回的内…

Android Activity和Intent机制学习笔记

文章转自&#xff1a;http://www.cnblogs.com/feisky/archive/2010/01/16/1649081.html Activity Android中&#xff0c;Activity是所有程序的根本&#xff0c;所有程序的流程都运行在Activity之中&#xff0c;Activity具有自己的生命周期&#xff08;见http://www.cnblogs.com…

scala中字符串计数_如何在Scala中创建一系列字符?

scala中字符串计数The range is a set of data from a lower value to a larger value. In Scala, we have an easy method to create a range using to keyword. 范围是从较低值到较大值的一组数据。 在Scala中&#xff0c;我们有一种使用to关键字创建范围的简单方法。 Synta…

多域名解析到同一网站C的php重定向代码

在index.php最前面加上以下代码&#xff1a; <?php if(strpos($_SERVER[HTTP_HOST],afish.cnblogs.com)false){#header(Location: http://afish.cnblogs.com/);echo <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org…

android 中 gravity 和 layout_gravity 的区别

文章转自&#xff1a;http://blog.csdn.net/feng88724/article/details/6333809 在进行UI布局的时候&#xff0c;可能经常会用到 android:gravity 和 android:layout_Gravity 这两个属性。 关于这两个属性的区别&#xff0c;网上已经有很多人进行了说明&#xff0c;这边再简…

线程的故事:我的3位母亲成就了优秀的我!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;声明&#xff1a;本故事纯属虚构&#xff0c;如果雷同那就是真事了&#xff01;大家好&#xff0c;我是线程&#xff0c;我的…

c ++查找字符串_C ++结构| 查找输出程序| 套装3

c 查找字符串Program 1: 程序1&#xff1a; #include <iostream>#pragma pack(1)using namespace std;typedef struct{int A;int B;char c1;char c2;} S;int main(){cout << sizeof(S);return 0;}Output: 输出&#xff1a; In a 32-bit system: 10In a 64-bit sy…

7种内存泄露场景和13种解决方案!

前言Java通过垃圾回收机制&#xff0c;可以自动的管理内存&#xff0c;这对开发人员来说是多么美好的事啊。但垃圾回收器并不是万能的&#xff0c;它能够处理大部分场景下的内存清理、内存泄露以及内存优化。但它也并不是万能的。不然&#xff0c;我们在实践的过程中也不会出现…

Android如何关闭Application

转载自&#xff1a;http://blog.csdn.net/hello_kevinkuang/article/details/7443005 程序启动后&#xff0c;先执行Application.onCreate()&#xff0c;再执行Activity.onCreate()。如果没有生成自己的Application&#xff0c;那么系统会为你自动生成一个。退出程序时我们一般…