类加载机制

概述 

所谓机制就是某种流程规范或运作模式。简单来说,将类文件加载到JVM中的过程,需要对这个过程进行限定和约束,这就是Java类加载的机制。

具体说来,对Java类加载机制的描述可以从三个方面:

  • 按需加载 需要某一个类时才会加载
  • 加载时,加载器的选择由双亲委派机制决定
  • 加载的具体流程

类加载器

按照java虚拟机规范中的定义,类加载器分为2大类:

  • 引导类加载器(bootstrap classLoader)
  • 自定义加载器(派生自抽象类classLoader的所有类: 拓展类加载器,应用类加载器,自定义加载器)

每一种类加载器都有其特定的职能,比如引导类加载器主要负责加载核心类库,如rt.jar,扩展类加载器负责lib/ext,应用类加载器负责类路径classpath下的类。这样设计的好处是实现了模块化管理和加载类的隔离

加载机制

双亲委派

其实就是类加载时的加载策略,因为jvm中类加载器不止一个,且分工不同,多个类加载器如何协同工作呢?所以jvm中定义了一个策略:

优先以上级加载为准,只加载一次,这种策略保证了java核心类库的安全,也保证了基础类型的一致性,外部同名的类不会污染核心类库。

java中类加载器的顶级类ClassLoader,所有自定义类加载器都继承自它,所以它的类加载流程就是java类加载的默认流程,即双亲委派。看源码:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// 检查类是否已加载Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();// 优先使用父类try {// 向上找父类加载器,直到 bootstraploaderif (parent != null) { // parent也是一个ClassLoader,所以会继续循环向上委派c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}//然后才是自己if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

所以,想要打破这种加载机制,核心是调整loadClass()逻辑

loadClass(){//不去调用super.loadClass,否则还是先向上委派加载// 优先自己加载try{findClass();}finally{// 自己加载不了,决定是否向上委派(自定义来源的加载器,通常向上委派也没用)super.loadClass();}
}
Class<?> findClass(){// 从网络、文件系统等
}

自定义类加载器

从网络中加载类,需要自定义类加载器。

自定义类加载器通常需要重写classLoader中的loadClass()findClass()方法。

// 自定义加载逻辑:何时使用自定义类加载器
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {Class<?> c = findLoadedClass(name);if (c == null && useMyClassLoaderLoad.contains(name)){//特殊的类让我自己加载c = findClass(name);if (c != null){return c;}}return super.loadClass(name);
}
// 自定义Class的寻找过程
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {//return super.findClass(name);//根据文件系统路径加载class文件,并返回byte数组byte[] classBytes = getClassByte(name);//调用ClassLoader提供的方法,将二进制数组转换成Class类的实例return defineClass(name, classBytes, 0, classBytes.length);
}

网络类加载器子类必须定义方法findClass和loadClassData来从网络加载类。一旦它下载了组成类的字节,它应该使用方法defineClass来创建一个类实例

ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();// 自定义加载器
class NetworkClassLoader extends ClassLoader {String host;int port;public Class findClass(String name) {byte[] b = loadClassData(name);// 使用defineClass创建一个Class实例return defineClass(name, b, 0, b.length);}// 从网络获取类文件的二进制数据private byte[] loadClassData(String name) {// load the class data from the connection. . .}
}

实现热部署

public class MyClassLoader extends ClassLoader {//用于读取.Class文件的路径private String swapPath;//用于标记这些name的类是先由自身加载的private Set<String> useMyClassLoaderLoad;public MyClassLoader(String swapPath, Set<String> useMyClassLoaderLoad) {this.swapPath = swapPath;this.useMyClassLoaderLoad = useMyClassLoaderLoad;}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {Class<?> c = findLoadedClass(name);if (c == null && useMyClassLoaderLoad.contains(name)){//特殊的类让我自己加载c = findClass(name);if (c != null){return c;}}return super.loadClass(name);}@Overrideprotected Class<?> findClass(String name) {//根据文件系统路径加载class文件,并返回byte数组byte[] classBytes = getClassByte(name);//调用ClassLoader提供的方法,将二进制数组转换成Class类的实例return defineClass(name, classBytes, 0, classBytes.length);}private byte[] getClassByte(String name) {String className = name.substring(name.lastIndexOf('.') + 1, name.length()) + ".class";try {FileInputStream fileInputStream = new FileInputStream(swapPath + className);byte[] buffer = new byte[1024];int length = 0;ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();while ((length = fileInputStream.read(buffer)) > 0){byteArrayOutputStream.write(buffer, 0, length);}return byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();}return new byte[]{};}
}// 热部署的类
public class Test {public void printVersion(){System.out.println("当前版本是1哦");}
}// 定时任务持续扫描public static void main(String[] args) {//创建一个2s执行一次的定时任务new Timer().schedule(new TimerTask() {@Overridepublic void run() {String swapPath = MyClassLoader.class.getResource("").getPath() + "swap/";String className = "com.example.Test";//每次都实例化一个ClassLoader,这里传入swap路径,和需要特殊加载的类名MyClassLoader myClassLoader = new MyClassLoader(swapPath, Sets.newHashSet(className));try {//使用自定义的ClassLoader加载类,并调用printVersion方法。Object o = myClassLoader.loadClass(className).newInstance();o.getClass().getMethod("printVersion").invoke(o);} catch (InstantiationException |IllegalAccessException |ClassNotFoundException |NoSuchMethodException |InvocationTargetException ignored) {}}}, 0,2000);}

加载与卸载

从外部加载包括:文件系统,网络

public class Plugin01 {public void print(){System.out.println("Plugin01执行");}
}//maven打包
<build><finalName>plugin01</finalName>
</build>
/*** 类加载器-模块插拔* 1.自定义几个类,模拟不同插件* 2.通过自定义类加载器加载* 3.卸载则是通过GC来完成* 4.通过VisualVM查看是否卸载成功** @author wangyuanye* @date 2024/8/3**/
public class LoadUnloadDemo {// 外部jar路径static String path1 = "/Users/mars/code/java/demo-classloader/demo-plugin01/target/plugin01.jar";static String path2 = "/Users/mars/code/java/demo-classloader/demo-plugin02/target/plugin02.jar";public static void main(String[] args) throws Exception{File plugin01 = new File(path1);File plugin02 = new File(path2);URL plugin01Url = plugin01.toURI().toURL();URL plugin02Url = plugin02.toURI().toURL();// 加载plugin1MyPluginClassloader classloader1 = new MyPluginClassloader(new URL[]{plugin01Url});Class<?> plugin01Class = classloader1.loadClass("org.wyy.Plugin01");Object o1 = plugin01Class.newInstance();plugin01Class.getMethod("print").invoke(o1);// 加载plugin2MyPluginClassloader classloader2 = new MyPluginClassloader(new URL[]{plugin02Url});Class<?> plugin02Class = classloader2.loadClass("org.wyy.Plugin02");Object o2 = plugin02Class.newInstance();plugin02Class.getMethod("print").invoke(o2);// 卸载o1 = null;o2 = null;// 1.使用软引用包装,观察回收情况,需要代码中调用GCSystem.gc();WeakReference<Object> p1 = new WeakReference<>(o1);WeakReference<Object> p2 = new WeakReference<>(o2);if (p1.get() == null) {System.out.println("plugin1 has been unloaded.");} else {System.out.println("plugin1 is still loaded.");}if (p2.get() == null) {System.out.println("plugin2 has been unloaded.");} else {System.out.println("plugin2 is still loaded.");}// 2.或使用visualVM观察,可在visualVM中手动GCThread.sleep(500000);}
}

GC前:

GC后:

plugin被卸载

堆Dump:

Tomcat中的类加载

官网:类加载器工作原理

Tomcat 在初始化时创建以下类加载器:

Bootstrap

此类加载器包含 Java 虚拟机提供的基本运行时类,以及系统扩展目录 ( $JAVA_HOME/jre/lib/ext) 中 JAR 文件中的任何类。

System

此类加载器通常根据CLASSPATH环境变量的内容进行初始化。所有此类类对于 Tomcat 内部类和 Web 应用程序均可见。

Common

此类加载器包含对 Tomcat 内部类和所有 Web 应用程序均可见的附加类。

WebappX

为部署在单个 Tomcat 实例中的每个 Web 应用程序创建一个类加载器。Web/WEB-INF/classes应用程序目录中所有未打包的类和资源以及 Web 应用程序目录下 JAR 文件中的类和资源都只/WEB-INF/lib对该 Web 应用程序可见,而对其他 Web 应用程序不可见。

Webapp ClassLoader的加载机制

WebappClassLoaderBaseloadClass() 流程如下:

// 1.尝试从已加载的类缓存中获取
// 2.首先使用引导类加载(防止覆盖java se中的类)
ClassLoader javaseLoader = getJavaseClassLoader();
// 3.加载失败,继续判断是否开启了 委派
if (delegateLoad) {clazz = Class.forName(name, false, parent);
}
// 4.webappClassLoader自己从/WEB-INF/下尝试获取
clazz = findClass(name);
// 5.委派给父类
if (!delegateLoad) {clazz = Class.forName(name, false, parent);
}
从 Web 应用程序的角度来看,类或资源加载按以下顺序查找以下存储库:JVM 的引导类
你的 Web 应用程序的/WEB-INF/classes
您的 Web 应用程序的/WEB-INF/lib/*.jar
系统类加载器类(如上所述)
常见的类加载器类(如上所述)
如果 Web 应用程序类加载器 配置了 <Loader delegate="true"/> 以下顺序:JVM 的引导类
系统类加载器类(如上所述)
常见的类加载器类(如上所述)
你的 Web 应用程序的/WEB-INF/classes
您的 Web 应用程序的/WEB-INF/lib/*.jar

总结:

  1. 始终是JVM的引导类加载器优先加载,防止污染java 核心类
  2. 开启委派则由父类尝试加载,然后是自己
  3. 未开启则先自己,再父类。

同名类在JVM是否冲突

在JVM中,类是通过类加载器实例和类的全限定名(包括包名和类名)的组合来唯一标识的。因此,即使两个类具有相同的全限定名,如果它们由不同的类加载器实例加载,它们在JVM中也会被视为不同的类。

如果试图使用同一个类加载器实例加载两个具有相同全限定名的类,那么JVM会抛出ClassNotFoundExceptionLinkageError,因为在同一个类加载器的命名空间内,一个类的全限定名必须是唯一的。

代码演示加载同名类到JVM中

// 自定义类加载器
public class CustomClassLoader extends ClassLoader{private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] data = transFile2Byte(name);return defineClass(name, data, 0, data.length);} catch (Exception e) {e.printStackTrace();}return null;}// 处理文件流private byte[] transFile2Byte(String name) throws Exception {String path = name.replace('.', '/').concat(".class");FileInputStream fileInputStream = new FileInputStream(classPath + "/"  + path);int len = fileInputStream.available();byte[] data = new byte[len];fileInputStream.read(data);fileInputStream.close();return data;}@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {if (!name.startsWith("org.mars.frame")) {c = this.getParent().loadClass(name);} else {c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}}
}// 测试加载同名类
public static void main(String[] args) throws Exception {String path = "/Users/mars/codeSpace/0bat/3source_code/7tomcat-demo/frame1/target/classes/";String path2 = "/Users/mars/codeSpace/0bat/3source_code/7tomcat-demo/frame2/target/classes/";CustomClassLoader classLoader = new CustomClassLoader(path);Class<?> clazz = classLoader.loadClass("org.mars.frame.Utils");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("info", null);method.invoke(obj, null);System.out.println(obj.getClass().getName());System.out.println(clazz.getClassLoader().getClass().getName());System.out.println("==================");CustomClassLoader classLoader2 = new CustomClassLoader(path2);Class<?> clazz2 = classLoader2.loadClass("org.mars.frame.Utils");Object obj2 = clazz2.newInstance();Method method2 = clazz2.getDeclaredMethod("info", null);method2.invoke(obj2, null);System.out.println(obj.getClass().getName());System.out.println(clazz2.getClassLoader().getClass().getName());
}// 执行结果
FamousFrame v1.0
org.mars.frame.Utils
load_policy.CustomClassLoader
==================
FamousFrame v2.0
org.mars.frame.Utils
load_policy.CustomClassLoader

欢迎评论、收藏,请勿转载!!!

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

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

相关文章

Web开发-html篇-上

HTML发展史 HTML的历史可以追溯到20世纪90年代初。当时&#xff0c;互联网尚处于起步阶段&#xff0c;Web浏览器也刚刚问世。HTML的创建者是蒂姆伯纳斯-李&#xff08;Tim Berners-Lee&#xff09;&#xff0c;他在1991年首次提出了HTML的概念。HTML的初衷是为了方便不同计算机…

python常用库

目录 tqdm库介绍用法 argparse库介绍用法 tqdm库 介绍 封装一个可视化&#xff0c;可拓展的进度条&#xff0c;以了解项目运行的时长&#xff0c;了解项目进展情况。 传入第 用法 安装 pip install tqdm1直接使用 for i in tqdm(range(1000)):time.sleep(0.01)等价 for i…

DNS处理模块 dnspython

DNS处理模块 dnspython 标题介绍安装dnspython 模块常用方法介绍实践&#xff1a;DNS域名轮询业务监控 标题介绍 Dnspython 是 Python 的 DNS 工具包。它可用于查询、区域传输、动态更新、名称服务器测试和许多其他事情。 dnspython 模块提供了大量的 DNS 处理方法&#xff0c…

django集成pytest进行自动化单元测试实战

文章目录 一、引入pytest相关的包二、配置pytest1、将django的配置区分测试环境、开发环境和生产环境2、配置pytest 三、编写测试用例1、业务测试2、接口测试 四、进行测试 在Django项目中集成Pytest进行单元测试可以提高测试的灵活性和效率&#xff0c;相比于Django自带的测试…

PyQt5入门

Python中经常使用的GUI控件集有PyQt、Tkinter、wxPython、Kivy、PyGUI和Libavg。其中PyQt是Qt(c语言实现的)为Python专门提供的扩展 PyQt是一套Python的GUI开发框架,即图形用户界面开发框架.。而在Python中则使用PyQt这一工具包&#xff08;PyQt5、PyQt5-tools、PyQt5-stubs&am…

卡码网--数组篇(二分法)

系列文章目录 文章目录 系列文章目录前言数组二分查找 前言 详情看&#xff1a;https://programmercarl.com/ 总结知识点用于复习 数组 概念: 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便的通过下标索引的方式获取到下标对应的数据。 特点&#xff1a;…

安卓基本布局(下)

TableLayout 常用属性描述collapseColumns设置需要被隐藏的列的列号。shrinkColumns设置允许被伸缩的列的列号。stretchColumns设置允许被拉伸的列的列号。 <TableLayout xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/TableL…

状体管理-装饰器

State 自己的状态 注意:不是状态变量的所有更改都会引起刷新。只有可以被框架观察到的修改才会引起UI刷新。 1、boolean、string、number类型时&#xff0c;可以观察到数值的变化。 2、class或者Object时&#xff0c;可以观察 自身的赋值 的变化&#xff0c;第一层属性赋值的变…

CC++:贪吃蛇小游戏教程

❀创作不易&#xff0c;关注作者不迷路❀&#x1f600;&#x1f600; 目录 &#x1f600;贪吃蛇简介 &#x1f603;贪吃蛇的实现 &#x1f40d;生成地图 &#x1f40d;生成蛇模块 ❀定义蛇的结构体 ❀初始化蛇的相关信息 ❀初始化食物的相关信息 &#x1f40d;光标定位和…

[Spring] SpringBoot统一功能处理与图书管理系统

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

USB 2.0 规范摘录

文章目录 1、USB 体系简介2、USB 数据流模型四种传输类型 3、USB 物理规范和电气规范4、USB 协议层规范事务传输&#xff08;Transaction&#xff09;的流程 5、USB 框架6、USB 主机&#xff1a;硬件和软件7、USB HUB 规范数据的转发唤醒信号的转发USB HUB 的帧同步HUB Repeate…

前端常见场景、JS计算精度丢失问题(Decimal.js 介绍)

目录 一. Decimal.js 介绍 二. 常用方法 1. 创建 Decimal 实例 2.加法 add 或 plus 3.减法 sub 或 minus 4.乘法 times 或 mul 5.除法 div 或 dividedBy 6.取模 7.幂运算 8.平方根 9.保留小数位 toFixed方法(四舍五入) 三.项目应用 前端精度丢失问题通常由以下原因…

【Kubernetes】kubeadmu快速部署k8s集群

目录 一.组件部署 二.环境初始化 三.所有节点部署docker&#xff0c;以及指定版本的kubeadm 四.所有节点安装kubeadm&#xff0c;kubelet和kubectl 五.高可用配置 六.部署K8S集群 1.master01 节点操作 2.master02、master03节点 3.master01 节点 4.master02、master…

C语言 ——— 学习、使用 strcmp函数 并模拟实现

目录 strcmp函数的功能 学习strcmp函数​编辑 使用strcmp函数 模拟实现strcmp函数 strcmp函数的功能 strcmp函数的功能是字符串比较&#xff0c;两个字符串的对应位置的字符进行比较&#xff0c;直到字符不同或达到终止的 \0 字符为止 举例说明&#xff1a; 字符串1&am…

leetcode-二叉树oj题1(共三道)--c语言

目录 a. 二叉树的概念以及实现参照博客&#xff1a; 一、三道题的oj链接 二、每题讲解 1.单值二叉树 a. 题目&#xff1a; b. 题目所给代码 c. 思路 d. 代码&#xff1a; 2. 相同的树 a. 题目 b. 题目所给代码 c. 思路 d. 代码 3. 二叉树的前序遍历 a. 题目 b.…

前端-05-VSCode自定义代码片段console.log(js/ts配置)、代码段快捷提示放在首位

目录 配置VSCode自定义代码片段console.log()log代码段快捷提示放在首位 配置VSCode自定义代码片段console.log() 点击VSCode左下角设置图标&#xff0c;点击用户代码片段 点击用户代码片段后&#xff0c;VSCode上方出现弹窗如下图&#xff08;没有显示这两个文件的话搜索一下…

Redis结合Lua脚本的简单使用

我们就拿购物车举例子 现在有5个东西免费送&#xff0c;我们只能选择1个 例如 可乐 美年达 香蕉 苹果 薯片 我们选择后就放进redis里面 然后我们不能选重复&#xff0c;只能选不同 Lua脚本 我们redis使用lua脚本的时候&#xff0c;会传两个参数进去 一个是List<Strin…

MySQL:数据库权限与角色

权限 MySQL 的权限管理系统是保障数据库安全性的关键组件之一。它允许数据库管理员精确控制哪些用户可以对哪些数据库对象执行哪些操作。 自主存取控制 DAC&#xff08;DiscretionaryAccess Control)&#xff1a;用户对于不同的数据库对象有不同的存取权限&#xff0c;不同的…

fatal: Could not read from remote repository. 解决方法

问题描述&#xff1a; Git : fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists。 解决方法&#xff1a; 当在网上尝试大量方法仍然失败的时候&#xff0c;不妨试试这个方法。 在 github 上&…

thinkphp框架远程代码执行

一、环境 vulfocus网上自行下载 启动命令&#xff1a; docker run -d --privileged -p 8081:80 -v /var/run/docker.sock:/var/run/docker.sock -e VUL_IP192.168.131.144 8e55f85571c8 一定添加--privileged不然只能拉取环境首页不显示 二、thinkphp远程代码执行 首页&a…