Java ClassLoader虚拟类实现代码热替换的示例代码

目录
  • 总结
  • ClassLoader 虚拟类方法
  • 实现代码热替换
  • 实现
  • 改进思考

总结

  1. 类加载器是负责加载类的对象。类ClassLoader是一个抽象类。给定类的全限定类名,类加载器应尝试查找或生成构成该类定义的数据Class文件。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的类文件
  2. 每个Class对象都包含一个Class.getClassLoader()方法可以获取到定义它的ClassLoader
  3. 数组类的Class对象不是由类加载器创建的,而是根据Java运行时的要求自动创建的。getClassLoader()返回的数组类的类装入器与其元素类型的类装入器相同,如果元素类型是基础类型,则数组类没有类装入器。
  4. 除了加载类之外,类加载器还负责定位资源。资源是一些数据(例如 .class文件、配置数据或图像)。资源通常与应用程序或库一起打包,以便可以通过应用程序或库中的代码找到它们。
  5. ClassLoader类使用委托模型来搜索类和资源。ClassLoader的每个实例都有一个关联的父类加载器。当请求查找类或资源时,ClassLoader实例通常会在尝试查找类或资源本身之前,将对该类或资源的搜索委托给其父类装入器。
  6. Java内置类加载器
加载器名 方法名 作用
Bootstrap class loader 虚拟机的内置类加载器,底层是用C++实现的,没有父加载器。主要加载系统环境的一些jar包和.class文件。C/C++语言编写,是虚拟机的一部分,无法在Java代码中直接获取它的引用。可以通过 System.getProperty(“sun.boot.class.path”)获取其加载路径下的文件
Platform class loader (也称为ExtClassLoader) getPlatformClassLoader() 平台类加载器,负责加载JDK中一些特殊的模块。主要加载java.ext.dirs下的.class文件
System class loader (也称为AppClassLoader) getSystemClassLoader() 系统类加载器,负责加载用户类路径上所指定的类库。主要加载java.class.path下的.class文件,是面向用户编写类的类加载器,即自己写的类或者引入的第三方库通常由此加载器加载
  1. 通常Java虚拟机以依赖于平台的方式从本地文件系统加载类。但是,有些类可能不是源于文件;它们可能来自其他来源,如网络,也可能由应用程序构建。 方法defineClass(String name, byte[] b, int off, int len),将字节数组转换为类class的实例。这个新定义的类的实例可以使用Class.newInstance()方法创建
  2. 类加载器创建的对象的方法和构造函数可以引用其他类。为了确定引用的类,Java虚拟机调用最初创建该类的类加载器的loadClass方法

ClassLoader 虚拟类方法

方法名 作用
protected ClassLoader(String name, ClassLoader parent) 创建指定名称name的新类加载器,并使用指定的父类加载器parent进行委派
public String getName() 返回此类加载器的名称,如果此类加载器未命名,则返回null
public Class loadClass(String name, boolean resolve) 加载具有指定类名称的类,resolve为true表示解析类引用。loadClass方法会先调用getClassLoadingLock方法获取锁,再调用findLoadedClass方法检查类是否已加载,如果未加载,则往父加载器一直递归调用loadClass加载该类,如果父加载器也加载不了该类,才调用findClass方法获取Class对象,而findClass是虚拟方法由子类实现。其实现使用defineClass(String name, byte[] b, int off, int len)方法可以将class文件的字节数组转为Class对象,最后使用resolveClass方法进行类的链接。而由于获取该字节数组的方法是很多样的,所以类加载的方式也非常多样,如本地加载、网络加载、压缩包中加载、自己构建Class文件
protected Object getClassLoadingLock(String className) 返回类加载操作的锁对象。 如果此ClassLoader对象注册为支持并行,则该方法返回与指定类名 className关联的专用对象。否则该方法将返回此ClassLoader对象,即同一时间一个ClassLoader只能加载一个类
protected final Class findLoadedClass(String name) 如果Java虚拟机已将此加载程序记录为具有给定全限定类名称的类的初始加载程序,则返回具有给定全限定类名称的类。否则返回 null
protected Class findClass(String name) 查找具有指定全限定类名的类。这个方法是空方法应该被子类重写,并且被调用在检查请求类的父类加载器之后,loadClass方法将调用这个方法
protected final Class<?> defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) 将字节数组转换为具有给定保护域ProtectionDomain的类Class的实例。 如果指定的name以“java.”开头,它只能由getPlatformClassLoader()获取到的平台类加载器或其祖先定义(define),否则将抛出SecurityException。如果name不是null,则它必须等于字节数组b指定的类的全限定类名称,否则将抛出NoClassDefFoundError
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b, ProtectionDomain protectionDomain) 将字节缓冲区ByteBuffer转换为具有给定保护域ProtectionDomain的类Class的实例。其余和上面一样
protected final void resolveClass(Class c) 链接指定的类。类加载器可能会使用此方法链接类。如果类c已经被链接,那么这个方法直接返回。 否则,将按照Java语言规范执行一章中的描述链接该类

实现代码热替换

通过上面我们可以知道类加载流程是

  1. loadClass方法会先调用getClassLoadingLock方法获取锁
  2. 再调用findLoadedClass方法检查类是否已加载,如果已经加载则直接获取到该Class类对象,再判断该Class类对象是否需要链接(resolve),如果要链接进入resolveClass,链接完后直接返回。链接就是执行类加载过程中的验证、准备、解析这些过程
  3. 如果未加载,则往父加载器一直递归调用loadClass加载该类
  4. 如果父加载器也加载不了该类,才调用findClass方法获取Class对象而findClass是空方法由子类重写
  5. 其实现中会使用defineClass(String name, byte[] b, int off, int len)方法,该可以将class文件的字节数组转为Class对象
  6. 如果需要链接,最后使用resolveClass方法进行类的链接

实现

  1. 如果我们要实现代码热替换,那么就要使用defineClass方法加载新的类,所以最简单的实现就是直接使用defineClass方法将新的Class文件字节数组转为Class对象,再使用反射创建新对象并执行新方法
  2. 但是defineClass是protected方法,所以我们只能继承ClassLoader虚拟类才能调用该方法
  3. 最好的规范就是继承ClassLoader虚拟类,并实现其loadClass方法和findClass方法,并在loadClass方法中调用findClass方法,在findClass方法中再调用defineClass方法
  4. 为了便于理解,直接抛弃规范,直接自己写一个方法直接调用defineClass方法实现代码热替换

项目结构,out是编译出的class文件目录,由于Test就在src目录下,没有包名,则其全限定类名为Test

public class Test extends ClassLoader {
    public static void main(String[] args) throws Exception {
        while(true) {
            try {
                Test test = new Test();
                // 编译后的class文件位置 ./表示代码根目录
                String classFile = "./out/production/Java_hot_replace/Test.class";
                FileInputStream fis = new FileInputStream(classFile);
                byte[] bytes = new byte[1024*10];
                int len = fis.read(bytes);
                //将字节数组转为Class类对象 Test为全限定类名
                Class clazz = test.defineClass("Test", bytes, 0 ,len);
                //使用反射根据新的Class对象创建新对象,并执行其printStr方法
                Object object = clazz.newInstance();
                Method m = object.getClass().getMethod("printStr", new Class[] {});
                m.invoke(object, new Object[] {});
                Thread.sleep(2000);
            } catch(Exception e) {
                e.printStackTrace();
                try {
                    Thread.sleep(2000);
                } catch(InterruptedException ex) {

                }
            }
        }
    }

    public void printStr() {
        System.out.println("A");
    }
}

启动后修改代码,然后点重新编译

可以看到代码被热替换了

改进思考

  • 正常实现流程应该为继承ClassLoader虚拟类,并重写其loadClass方法和findClass方法,并在loadClass方法中调用findClass方法,在findClass方法中再调用defineClass方法
  • 实现热替换应该是替换修改过的代码,则应当维护一个Map<String, Long> 存储从全限定类名到上次文件修改时间的映射,每次定时扫描Class文件目录或检测到保存快捷键Ctrl+s时触发扫描,文件的属性也有上次修改时间,拿我们存储的和文件的属性比较即可知道文件是否修改,即是否需要重新加载Class类
  • 热替换产生了大量类信息都存储在jdk1.7的永久代,jdk1.8的元空间,如果无用的类信息过多则会造成OOM,我们自定义类加载器和其产生的Class类对象,都可以通过置空(= null)使其不可达,然后调用System.gc()就可以卸载,即类似如下代码
public class Test extends ClassLoader {
   public static void main(String[] args) throws Exception {
     	MyClassLoader classLoader = new MyClassLoader();
 	 	Class classLoaded = classLoader.loadClass("MyClass");
  	 	classLoaded = null;
  		classLoader = null;
  	 	System.gc();
  	}
}

到此这篇关于Java ClassLoader虚拟类实现代码热替换的示例代码的文章就介绍到这了,更多相关Java ClassLoader代码热替换内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Classloader隔离技术在业务监控中的应用详解

    目录 1. 背景&简介 2. 业务监控平台脚本调试流程 2.1 业务监控的脚本开发调试流程图 3. 自定义Classloder | 打破双亲委派 3.1 什么是Classloader 3.2 Classloader动态加载依赖文件 3.3 自定义类加载器 3.4 业务监控使用CustomClassloader 3.5 业务监控动态加载JAR和脚本的实现 4. 问题&原因&方案 5. 总结 1. 背景&简介 业务监控平台是得物自研的一款用于数据和状态验证的平台.能快速便捷发现

  • Python深度学习之简单实现猫狗图像分类

    一.前言 本文使用的是 kaggle 猫狗大战的数据集 训练集中有 25000 张图像,测试集中有 12500 张图像.作为简单示例,我们用不了那么多图像,随便抽取一小部分猫狗图像到一个文件夹里即可. 通过使用更大.更复杂的模型,可以获得更高的准确率,预训练模型是一个很好的选择,我们可以直接使用预训练模型来完成分类任务,因为预训练模型通常已经在大型的数据集上进行过训练,通常用于完成大型的图像分类任务. tf.keras.applications中有一些预定义好的经典卷积神经网络结构(Applic

  • SpringBoot详细讲解通过自定义classloader加密保护class文件

    目录 背景 maven插件加密 注意事项 自定义classloader 隐藏classloader 被保护class手动加壳 总结 背景 最近针对公司框架进行关键业务代码进行加密处理,防止通过jd-gui等反编译工具能够轻松还原工程代码,相关混淆方案配置使用比较复杂且针对springboot项目问题较多,所以针对class文件加密再通过自定义的classloder进行解密加载,此方案并不是绝对安全,只是加大反编译的困难程度,防君子不防小人,整体加密保护流程图如下图所示 maven插件加密 使用自

  • JVM中ClassLoader类加载器的深入理解

    JVM的体系结构图 先来看一下JVM的体系结构,如下图: JVM的位置 JVM的位置,如下图: JVM是运行在操作系统之上的,与硬件没有直接的交互,但是可以调用底层的硬件,用JIN(Java本地接口调用底层硬件) JVM结构图中的class files文件 class files文件,是保存在我们电脑本地的字节码文件,.java文件经过编译之后,就会生成一个.class文件,这个文件就是class files所对应的字节码文件,如下图: JVM结构图中的类加载器ClassLoader的解释 类加

  • java自定义ClassLoader加载指定的class文件操作

    继承ClassLoader并且重写findClass方法就可以自定义一个类加载器,具体什么是类加载器以及类加载器的加载过程与顺序下次再说,下面给出一个小demo 首先定义一个类,比如MyTest,并且将其编译成class文件,然后放到一个指定的文件夹下面,其中文件夹的最后几层就是它的包名,这里我将这个编译好的类放到 : /Users/allen/Desktop/cn/lijie/MyTest.class package cn.lijie; public class MyTest { public

  • Java源码解析之ClassLoader

    一.前言 一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在的,则会引发系统异常.而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它cl

  • Java ClassLoader虚拟类实现代码热替换的示例代码

    目录 总结 ClassLoader 虚拟类方法 实现代码热替换 实现 改进思考 总结 类加载器是负责加载类的对象.类ClassLoader是一个抽象类.给定类的全限定类名,类加载器应尝试查找或生成构成该类定义的数据Class文件.典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的类文件 每个Class对象都包含一个Class.getClassLoader()方法可以获取到定义它的ClassLoader 数组类的Class对象不是由类加载器创建的,而是根据Java运行时的要求自动创建的.

  • Java生成中间logo的二维码的示例代码

    最近有负责微信开发,对于微信开发的项目,肯定少不了二维码啦,正好有个这样的需求,这对不同的商品生成一个二维码,扫码即刻下单.博主就弄了一个二维码生成的工具类. 弄出来之后,产品经理又说了,中间放上公司的logo是不是好一点?加上吧, 加上公司logo之后,产品经理想了想,每个商品都有个二维码,销售人员有很多个商品二维码,群发给用户,在qq群上,微信群上,怎么知道哪个二维码对应哪个商品的呢?于是决定要加上商品名称.最后商品二维码就成了下面这个模样了(当然啦,这里面的logo并不是博主现职公司的).

  • java使用jar包生成二维码的示例代码

    使用java进行二维码的生成与读取使用到了谷歌的zxing.jar 第一步 导入,maven依赖或者下载指定jar包 <!-- https://mvnrepository.com/artifact/com.google.zxing/javase --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>javase</artifactId> <version

  • Java实现经典角色扮演侦探游戏游戏的示例代码

    目录 前言 游戏背景 主要需求 主要设计 功能截图 代码实现 游戏主界面 主卧 初始化 大厅 总结 前言 游戏背景 百变山庄坐落于太平洋的一座小岛上,山庄主人亦是小岛的主人.这个神秘主人细致周到,邀请函里不仅附着往返港口的机票,港口的邮船也是通往小岛的专线. 初登小岛,恢宏大气的山庄直入眼帘,通过门廊,金碧辉煌的大厅震人心魄. 受邀的侦探们陆续到齐,[侍者]彬彬有礼地站在一旁,他安排你们围坐在一个奇特十边形的桌子旁稍加等待.[侦探指尖]回忆着自己临行前调查的各位名侦探的资料,除了那个神秘的[电话

  • Java实现经典拳皇误闯冒险岛游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏主界面 英雄 总结 前言 <拳皇误闯冒险岛>是拳皇和冒险岛素材的基于JavaSwing的动作类游戏,独创改编. 主要需求 拳皇迷迷糊糊醒来,发现自己在一间废弃的工厂里,地上爬满怪兽..这么可爱的怪兽,一拳下去,应该会哭很久吧~拳皇心里吐槽了下,向怪兽的怀抱冲了上去~~ 主要设计 1.游戏面板生成显示 2.背景选用冒险岛素材图 3.设计英雄,包含生命值,法术值,英雄的动作变化处理,英雄的技能特效 4.设计怪兽,包含怪物血量,攻击力,位置,步长等 5.

  • Java实现把文件压缩成zip文件的示例代码

    实现代码 ackage org.fh.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; /** * 说明:java压缩成zip * 作者:FH Admin * from:fhadmin.cn */ public class Fi

  • Java实现泡泡堂对战版游戏的示例代码

    目录 前言 主要设计 功能截图 代码实现 游戏启动类 核心监听类 核心线程类 总结 前言 <泡泡堂II>是一个基于java的自制游戏,使用了MVC模式,分离了模型.视图和控制器,使得项目结构清晰易于扩展,使用配置文件来设置游戏基本配置,扩展地图人物道具等.同时,该程序编写期间用了单例模式.工厂模式.模板模式等设计模式.为了游戏的可玩性,特意设计了平滑碰撞以及机器人. 主要设计 设计游戏界面,用swing实现 绘制游戏启动界面.结束界面.地图.主角.道具 实现泡泡爆炸 为了尽量复原泡泡堂游戏,初

  • Java实现贪吃蛇大作战小游戏的示例代码

    目录 效果展示 项目介绍 项目背景 总体需求 实现过程 代码展示 项目结构 总结 大家好,今天尝试用swing技术写一个贪吃蛇大作战小游戏,供大家参考. 效果展示 效果展示 一.游戏界面 二.得分情况 项目介绍 项目背景 “贪吃蛇大作战”游戏是一个经典的游戏,它因操作简单.娱乐性强,自从计算机实现以来,深受广大电脑玩家的喜爱,本项目基于Java技术,开发了一个 操作简单.界面美观.功能较齐全 的“贪吃蛇”游戏.通过本游戏的开发,达到学习Java技术和熟悉软件开发流程的目的. 总体需求  本系统主

  • Java实现断点下载服务端与客户端的示例代码

    目录 原理 扩展-大文件快速下载思路 代码 服务端 客户端 最近在研究断点下载(下载续传)的功能,此功能需要服务端和客户端进行对接编写,本篇也是记录一下关于贴上关于实现服务端(Spring Boot)与客户端(Android)是如何实现下载续传功能 断点下载功能(下载续传)解释: 客户端由于突然性网络中断等原因,导致的下载失败,这个时候重新下载,可以继续从上次的地方进行下载,而不是重新下载 原理 首先,我们先说明了断点续传的功能,实际上的原理比较简单 客户端和服务端规定好一个规则,客户端传递一个

  • Java实现手写乞丐版线程池的示例代码

    目录 前言 线程池的具体实现 线程池实现思路 线程池实现代码 线程池测试代码 杂谈 总结 前言 在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全. 线程池的具体实现 线程池实现思路 任务保存到哪里? 在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理.在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务

随机推荐