厉害了,自己手写一个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,一经查实,立即删除!

相关文章

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

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自…

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

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

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…

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

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

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

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

Android Sqlite

2019独角兽企业重金招聘Python工程师标准>>> 一、http://lansuiyun.iteye.com/blog/1246430 good! 二、增加新表时&#xff0c;需要更新版本号。 三、Android 使用自定义cursorAdapter&#xff1a; http://blog.csdn.net/buaalei/article/details/6064792 good! 四…

小黑小波比.git clone报错解决方案

2019独角兽企业重金招聘Python工程师标准>>> zmzpzmzp1:~/data$ git clone git192.168.199.199:zmw/s910.git 正克隆到 s910... ssh: connect to host 192.168.199.199 port 22: Connection refused fatal: Could not read from remote repository.Please make sure…

一个sql注入直接把我们服务搞挂了

前言最近我在整理安全漏洞相关问题&#xff0c;准备在公司做一次分享。恰好&#xff0c;这段时间团队发现了一个sql注入漏洞&#xff1a;在一个公共的分页功能中&#xff0c;排序字段作为入参&#xff0c;前端页面可以自定义。在分页sql的mybatis mapper.xml中&#xff0c;orde…

reinterpret_cast和static_cast的总结

主要参考&#xff1a;http://blog.csdn.net/querw/article/details/7387594 http://www.cnblogs.com/jerry19880126/archive/2012/08/14/2638192.html http://www.cnblogs.com/ider/archive/2011/07/30/cpp_cast_operator_part3.htmlhttp://bbs.csdn.net/topics/390249118 关键…

Java双刃剑之Unsafe类详解

前一段时间在研究juc源码的时候&#xff0c;发现在很多工具类中都调用了一个Unsafe类中的方法&#xff0c;出于好奇就想要研究一下这个类到底有什么作用&#xff0c;于是先查阅了一些资料&#xff0c;一查不要紧&#xff0c;很多资料中对Unsafe的态度都是这样的画风&#xff1a…

Java知多少(66)输入输出(IO)和流的概述

输入输出&#xff08;I/O&#xff09;是指程序与外部设备或其他计算机进行交互的操作。几乎所有的程序都具有输入与输出操作&#xff0c;如从键盘上读取数据&#xff0c;从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息&#xff0c;或者是把信…

额!Java中用户线程和守护线程区别这么大?

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 语言中线程分为两类&#xff1a;用户线程和守护线程&#xff0c;而二者之间的区别却鲜有人知&#xff0c;所以本文磊…

EasyUI-右键菜单变灰不可用效果

使用过EasyUI的朋友想必都知道疯狂秀才写的后台界面吧&#xff0c;作为一个初学者我不敢妄自评论它的好坏&#xff0c;不过它确实给我们提供了一个很好框架&#xff0c;只要在它的基础上进行修改&#xff0c;基本上都可以满足我们开发的需要。 知道“疯狂秀才”写的后台界面已经…

ThreadLocal中的3个大坑,内存泄露都是小儿科!

我在参加Code Review的时候不止一次听到有同学说&#xff1a;我写的这个上下文工具没问题&#xff0c;在线上跑了好久了。其实这种想法是有问题的&#xff0c;ThreadLocal写错难&#xff0c;但是用错就很容易&#xff0c;本文将会详细总结ThreadLocal容易用错的三个坑&#xff…

基于.Net的单点登录(SSO)解决方案

为什么80%的码农都做不了架构师&#xff1f;>>> 前些天一位朋友要我帮忙做一单点登录&#xff0c;其实这个概念早已耳熟能详&#xff0c;但实际应用很少&#xff0c;难得最近轻闲&#xff0c;于是决定通过本文来详细描述一个SSO解决方案&#xff0c;希望对 大家有所…

在android中ScrollView嵌套ScrollView解决方案

文章转载自&#xff1a;http://www.jb51.net/article/33054.htm大家好&#xff0c;众所周知&#xff0c;android里两个相同方向的ScrollView是不能嵌套的&#xff0c;那要是有这样的需求怎么办,接下来为您介绍解决方法&#xff0c;感兴趣的朋友可以了解下大家好&#xff0c;众所…

Cucumber 入门一

&#xff08;转自&#xff1a;http://www.cnblogs.com/jarodzz/archive/2012/07/02/2573014.html&#xff09; 第一次看到Cucumber和BDD&#xff08;Behavior Driven Development, 行为驱动开发&#xff09;&#xff0c;是在四年前。那时才開始工作&#xff0c;对软件測试工具相…

Java中这7个方法,一不小心就用错了!

最近我们通过sonar静态代码检测&#xff0c;同时配合人工代码review&#xff0c;发现了项目中很多代码问题。除了常规的bug和安全漏洞之外&#xff0c;还有几处方法用法错误&#xff0c;引起了我极大的兴趣。我为什么会对这几个方法这么感兴趣呢&#xff1f;因为它们极具迷惑性…