当前位置: 首页 > news >正文

JVM类加载机制—类加载器和双亲委派机制详解

一、概述

上篇我们介绍了JVM类加载机制—JVM类加载过程,类加载过程是类加载机制第一阶段,这一阶段主要做将类的字节码(class文件)加载JVM内存模型中,并转换为JVM内部的数据结构(如java.lang.Class实例),便于执行。其中类加载器是JVM用于加载类文件的一个子系统,主要是通过类的全限定名来定位和加载类的二进制文件。

二、类加载器

1、类加载器分类

JVM支持两种类型的类加载器,分别为JDK自带类加载器自定义类加载器。其中自带类加载器包括引导类加载器(启动类加载器/根类加载器)、扩展类加载器、应用程序类加载器。

引导类加载器(Bootstrap ClassLoader):也加启动类加载器或根类加载器。负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等。不继承java.lang.ClassLoader,是扩展类加载器的父加载器(不是父类)。底层有C++实现,在java代码中无法直接获取到引用,返回null。

扩展类加载器(Extension ClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包,是应用程序类加载器的父加载器。由java代码编写,继承ClassLoader类。

应用程序类加载器(Application ClassLoader):也称为系统类加载器,负责加载ClassPath路径下的类包,主要就是加载自己写的那些类。由java代码编写,继承ClassLoader类,为程序中默认的类加载器。

自定义类加载器(User-Defined ClassLoader):负责加载用户自定义路径下的类包,支持一些个性化的功能。自定义类加载器只需继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,还有一个方法是findClass,默认实现是空 方法,所以我们自定义类加载器主要是重写findClass方法。

自定义类加载器示例代码如下

import java.io.FileInputStream;/*** 自定义类加载器*/
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader {private String classPath;public MyClassLoader(String classPath) {this.classPath = classPath;}//根据类名从磁盘上将类文件 读取到byte[]数组中private byte[] loadByte(String className) throws Exception {className = className.replace("\\.", "/");FileInputStream fis = new FileInputStream(classPath + "/" + className + ".class");int length = fis.available();byte[] bytes = new byte[length];fis.read(bytes);fis.close();return bytes;}//根据类名 加载Class类protected Class<?> findClass(String name) throws ClassNotFoundException {byte[] bytes= null;try {bytes = loadByte(name);//defineClass方法将一个字节数组转换为Class对象,这个字节数组是class文件读取后最终的字节数组return defineClass(name, bytes, 0, bytes.length);} catch (Exception e) {throw new ClassNotFoundException();}}}public static void main(String[] args) throws Exception {//初始化类自定义类加载器,会先初始化父类ClassLoaderMyClassLoader myClassLoader = new MyClassLoader("D:/test");//D盘创建test/com/test/jvm/ 几级目录,将User1.class放入该目录Class clazz = myClassLoader.loadClass("com.test.jvm.User1");Object object = clazz.newInstance();//通过反射获取类中sout方法Method method = clazz.getDeclaredMethod("sout", new Class<?>[]{});//执行该方法method.invoke(object, null);//打印这个类的类加载器 MyClassLoaderTest$MyClassLoader System.out.println(clazz.getClassLoader().getClass().getName());}
}

运行结果:

通过下面示例可以测试下类所使用的具体类加载器

/*** 类加载器过程*/
public class TestJDKClassLoader {public static void main(String[] args) {//核心程序类 支撑JVM运行 位于lib目录下  引导类加载器System.out.println("引导类加载器:" +String.class.getClassLoader());//扩展程序类 支撑JVM运行 位于lib目录中ext下的包 扩展类加载器System.out.println("扩展类加载器:" +DESKeyFactory.class.getClassLoader());//自己编写的程序 运行自身业务流程  应用程序类加载器System.out.println("应用程序类加载器:" +TestJDKClassLoader.class.getClassLoader());}
}

运行结果

通过上面结果我们可以看出 引导类加载器底层由于是C++实现,在java中无法获取,而扩展类加载器和应用程序类加载器 都是sun.misc.Launcher类下的内部类。

引导类加载器是扩展类加载器的父加载器,扩展类加载器是应用程序类加载器的父加载器,但三者之间没有继承或实现的关系。

2、类加载初始化过程

通过类加载全过程图(加载过程)知道,类加载会创建JVM的sun.misc.Launcher类实例的启动器。sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。通过Launcher类的getLauncher()方法来获取类自己的加载器ClassLoader。

getLauncher()源码

public class Launcher {private static URLStreamHandlerFactory factory = new Factory();//单例模式设计,保证一个JVM中只有一个Launcher实例private static Launcher launcher = new Launcher();private static String bootClassPath = System.getProperty("sun.boot.class.path");private ClassLoader loader;private static URLStreamHandler fileHandler;//获取Launcher类实例public static Launcher getLauncher() {return launcher;}//Launcher类构造方法public Launcher() {ExtClassLoader var1;try {//构造扩展类加载器,在构造的过程中将其父加载器设置为nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {//构造应用程序类加载器,在构造过程中将其父加载器设置为ExtClassLoader//Launcher类的loader属性只是AppClassLoader,一般都是用这个类加载器来加载我们写的代码this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");//。。。。。省略一部分不关注的代码}
}

三、JVM类加载机制种类

全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。


双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。


缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

四、双亲委派机制

JVM为了避免类的重复加载,确保一个类的全局唯一性以及保护程序安全,防止核心API被随意篡改。JVM会采用双亲委派模型进行加载,双亲委派模型要求除了引导类加载器外,其余的类加载器都应当有自己的父加载器。

1、工作原理

当类加载器收到加载一个类的请求时,它首先不会尝试自己去加载这个类,而是为先委托父加载器寻找这个类,找不到就会在委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。简单来说先找父加载器加载,不行再由子加载器自己加载。加载流程如下:

我们来看下应用程序类加载器APPClassLoader加载类的双亲委派机制的源码。APPClassLoader加载器的loadClass方法最终会调用其父类ClassLoader的loadClass方法。该方法大体逻辑如下:

1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。

2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false));或者是调用bootstrap类加载器来加载。

3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。

ClassLoader.loadClass()源码

//ClassLoader的loadClass方法,里面实现了双亲委派机制
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded//首先,检查当前类加载器起是否已经加载了该类,若当前加载器集合中有该类 直接返回//第一次加载该类 肯定为空Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//如果当前类加载器的父加载器不为空,则委托父加载器加载该类//AppClassLoader 父加载器为ExtClassLoader,直接执行还是到这个loadClass方法ExtClassLoader加载器的父加载器为null(引导类加载器是C++实现,所以为null)if (parent != null) {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}//当该类通过类加载器加载为空时,通过findClass方法加载类//ExtClassLoader无findClass方法,通过父类URLClassLoader.findClass方法加载类if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//通过父类URLClassLoader.findClass方法加载类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;}}

URLClassLoader.findClass方法源码

//类加载器通过findClass方法尝试加载类
protected Class<?> findClass(final String name)throws ClassNotFoundException{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {//通过类的name 获取类的路径//如com.test.jvm.Math 获取path = com/text/jvm/Math.classString path = name.replace('.', '/').concat(".class");//通过类路径从磁盘上拿到类文件Resource res = ucp.getResource(path, false);if (res != null) {try {//通过defineClass方法将类装载到JVM中//ExtClassLoader加载器是装载失败的,因为ExtClassLoader加载的是ext包下的路径,返回为null//再会执行AppClassLoader加载器的defineClass进行加载,才会装载JVM成功return defineClass(name, res);} catch (IOException e) {throw new ClassNotFoundException(name, e);}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;}

2、双亲委派机制意义

沙箱安全机制:确保一个类的全局唯一性以及保护程序安全,防止核心API被随意篡改。如自己写的java.lang.String.class类是不会被加载的

类的唯一性:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,避免重复加载,保证被加载类的唯一性

上一篇: JVM类加载机制—类加载过程

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Mybatis框架常见问题总结
  • 计算机毕业设计Spark+Tensorflow股票推荐系统 股票预测系统 股票可视化 股票数据分析 量化交易系统 股票爬虫 股票K线图 大数据毕业设计 AI
  • 基于YOLOv8的无人机高空红外(HIT-UAV)检测算法,新的混合型扩张型残差注意力(HDRAB)助力涨点(二)
  • 小琳AI课堂:AIGC
  • 香蕉梨:自然的甜蜜宝藏
  • C\C++ Sqlite3使用详解
  • 云计算实训34——docker环境配置、镜像基本操作、容器基本操作、设置远程连接管理
  • wpf DynamicResource的ResourceKey值进行绑定
  • vue2版本空目录下创建新项目的方法2024
  • RocketMQ~刷盘机制、主从复制方式、存储机制
  • Nginx - 反向代理、缓存详解
  • msxml*.dll 错误 ‘80072f7d‘ 安全频道支持出错 解决方案
  • Linux网络配置和系统管理
  • 【发邮件】 在邮件中添加 (mailto:) 链接的返回电子邮件
  • 【Python】机器学习中的 K-均值聚类算法及其优缺点
  • 07.Android之多媒体问题
  • 11111111
  • Angular 响应式表单之下拉框
  • docker python 配置
  • Docker 笔记(1):介绍、镜像、容器及其基本操作
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • HashMap剖析之内部结构
  • Kibana配置logstash,报表一体化
  • mysql 数据库四种事务隔离级别
  • python_bomb----数据类型总结
  • vue-loader 源码解析系列之 selector
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • Vue全家桶实现一个Web App
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 不发不行!Netty集成文字图片聊天室外加TCP/IP软硬件通信
  • 翻译--Thinking in React
  • 正则学习笔记
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • 浅谈sql中的in与not in,exists与not exists的区别
  • ​软考-高级-系统架构设计师教程(清华第2版)【第15章 面向服务架构设计理论与实践(P527~554)-思维导图】​
  • # Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型
  • ######## golang各章节终篇索引 ########
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #我与Java虚拟机的故事#连载11: JVM学习之路
  • (175)FPGA门控时钟技术
  • (35)远程识别(又称无人机识别)(二)
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (pojstep1.3.1)1017(构造法模拟)
  • (PWM呼吸灯)合泰开发板HT66F2390-----点灯大师
  • (zhuan) 一些RL的文献(及笔记)
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (附源码)springboot教学评价 毕业设计 641310
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (黑马C++)L06 重载与继承
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (幽默漫画)有个程序员老公,是怎样的体验?
  • .form文件_一篇文章学会文件上传