JVM学习-详解类加载器(一)

类加载器

  • 类加载器是JVM执行类加载机制的前提
ClassLoader的作用
  • ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过各种方式将Class信息的二进制数据流读入JVM内部,转换为一个与目标类型对应的java.lang.Class对象实例,然后交给Java虚拟机进行链接,初始化等操作,因此ClassLoader整个装载阶段,只能影响类的加载,而无法通过ClassLoader去改变类的链接和初始化行为,至于是否可以运行,由Execution Engine决定
    在这里插入图片描述
  • 类加载器最早在java1.0版本中,那时只是单纯地为了满足Java Applet应用而被研发出来,但如今类加载器却在OSGi,字节码加解密领域大放异彩,这主要归功于Java虚拟机的设计者们当初在设计类加载器的时候,并没有考虑将它绑定在JVM内部,这样做的好处就是能够更加灵活和动态执行类加载操作。
类加载分类
显式加载
  • 显示加载指的是在代码中通过Classloader加载class对象,如直接使用Class.forName(name)或this.getClass().getClassLoader().loadClass()加载class对象
隐式加载
  • 隐式加载则不直接在代码中调用ClassLoader的方法加载class对象,而是通过虚拟机自动加载到内存中,如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时额外引用的类将通过JVM自动加载到内存中
类加载必要性
  • 避免在开发中遇到java.lang.ClassNotFoundException异常或java.lang.NoClassDefFoundError异常时,手足无措,只有了解类加载器的加载机制才能在出现异常时根据错误异常日志定位问题和解决问题
  • 需要支持类的动态加载或需要对编译后的字节码文件进行加密操作时,就需要与类加载器打交道了
  • 开发人员可以在程序中编写自定义类加载器来重新定义类的加载规则,以便实现一些自定义的处理逻辑
命名空间
何为类的唯一性
  • 对于任意一个类,都需要加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,**比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。**否则,即使这两个类源自同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等
命名空间
  • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有的父加载器所加载的类组成
  • 在同一个命名空间中,不会出现类的完整名字相同的两个类
  • 在不同的命名空间中,有可能会出现类的完整名字相同的两个类
类加载机制的基本特征
  • 双亲委派模型:不是所有类加载都遵守这个模型,启动类加载器所加载的类型,是可能要加载用户代码的,比如JDK内部的ServiceProvider/ServiceLoader机制,用户可以在标准API框架上,提供自己的实现,JDK也需要提供些默认的参考实现,如Java中JNDI,JDBC,文件系统,Cipher等很多方面,都是利用这种机制,这种情况不会用双亲委派模型去加载,而是利用所谓的上下文加载器
  • 可见性:子类加载器可以访问父类加载器加载的类型,但是反过来是不允许的,不然,因为缺少必要的隔离,我们就没有办法利用类加载器去实现容器的逻辑
  • 单一性:由于父加载器的类型对于子加载器可见,所以父加载器中加载过的类型,就不会在子加载器中重复加载,但是注意,类加载器“邻居”间,同一类型仍然可以被加载多次,因为互相并不可见。
类加载器
  • JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
  • 从概念上讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是Java虚拟机规范却没有这么定义,将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器,无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构如下:
    在这里插入图片描述
  • 除了顶层的启动类加载器外,其余的类加载器都应有自己的“父类”加载器
  • 不同类加载器看似是继承(Inheritance)关系,实际上是包含关系,在下层加载器中,包含着上层加载器的引用
引导类加载器
  • 这个类加载器使用C/C++语言实现,嵌套在JVM内部
  • 它用来加载Java核心类库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供JVM自身需要的类
  • 并不继承自java.lang.ClassLoader,没有父加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类
  • 加载扩展类加载器和应用程序类加载器,并指定为他们的父类加载器
  • 使用-XX:+TraceClassLoading
    在这里插入图片描述
扩展类加载器
  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  • 继承于ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库,如用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
    在这里插入图片描述
    类加载器实验
系统类加载器(应用程序类加载器)
  • java语言编写,由sun.misc.Launcher$AppClassLoader实现
  • 派生于ClassLoader类
  • 父类加载器为扩展类加载器ExtClassLoader
  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  • 该类为程序默认的类加载器,java应用的类由它来完成加载
  • 通过ClassLoader#getSystemClassLoader()方法可以获得此类加载器
用户自定义类加载器
  • 在Java的日常应用开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式
  • 体现Java语言的强大生命力和巨大魅力的关键因素之一便是,Java开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的JAR包,也可以是网络上的远程资源
  • 通过类加载器可以实现非常绝妙的插件机制,这方面的实际应用安全举不胜举,例如,著名的OSGI组件框架,再如Eclipse的插件机制,类加载器为应用程序提供一种动态增加新功能的机制,这种机制无须重新打包发布应用程序就能实现
  • 同时,自定义加载器能够实现应用隔离,例如Tomcat,Spring等中间件和组件框架都在内部实现自定义的加载器,并通过自定义加载器隔离不同的组件模块,这种机制比C/C++程序要好很多,想不修改C/C++程序就能为其新增功能,几乎是不可能的,仅一个兼容性便能阻挡住所有美好设想
  • 自定义类加载器通常需要继承于ClassLoader
ClassLoader源码解析
ClassLoader与现有类加载器的关系

在这里插入图片描述

  • 除了以上虚拟机自带的加载器外,用户还可以定制自己的类加载器,Java提供了抽象类java.lang.ClassLoader,所有用户自定义的类加载器继承ClassLoader
抽象类ClassLoader主要方法
  • public final ClassLoader getParent():返回类加载器的超类加载器
  • public Class<?> loadClass(String name) throws ClassNotFoundException :加载名称为name的类,返回结果为java.lang.Class类的实例,如果找不到类,则返回ClassNotFoundException异常,该方法中的逻辑就是双亲委派模式的实现
//测试代码ClassLoader.getSystemClassLoader().loadClass("com.chapter11.User")涉及以下方法调用
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {  //同步代码,保证多个线程只能有一个运行// 首先,在缓存中判断是否已经加载同名的类Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//获取当前类加载器的父类加载器,则调用父类加载器进行类的加载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}if (c == null) {   //当前类加载器的父类加载器未加载此类或当前类加载器未加载此类// 调用当前ClassLoader的findClass()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;}}
  • protected Class<?> findClass(String name) throws ClassNotFoundException:查找二进制名称为name的类,返回结果为java.lang.Class类的实例,这是一个受保护的方法,JVM鼓励我们重写此方法,需要自定义加载器遵循双亲委派机制,该方法会在检查完父类加载器之后被loadClass()方法调用
  • 在JDK1.2之前,在自定义类加载时,总会去继承ClassLoader类并重写loadClass方法,从而实现自定义的类加载类,1.2后不再建议用户去覆盖loadClass()方法,而是建议把自定义的类加载逻辑写在findClass()方法中,从前面分析可知,findClass()方法是在loadClass()方法中被调用,当loadClass()方法中父加载器加载失败后,则会调用自己findClass()方法来完成类加载,这样就可以保证自定义类加载器也符合双亲委派模式
  • ClassLoader类中并没有实现findClass()方法的具体代码逻辑,取而代之的是抛出ClassNotFoundException异常,同时应该知道的是findClass方法通常和defineClass方法一起使用,一般情况下,在自定义加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码后转换成流,然后调用defineClass()方法生成类的Class对象
//在URLClassLoader中重写了findClass方法,代码如下
protected Class<?> findClass(final String name)throws ClassNotFoundException{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {String path = name.replace('.', '/').concat(".class");Resource res = ucp.getResource(path, false);if (res != null) {try {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;}
  • protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError:根据给定的字节数组b转换为Class实例,off和len参数表示实际Class信息在byte数组中的位置和长度,其中byte数组b是ClassLoader从外部获取的,这是受保护的方法,只有在自定义ClassLoader子类中可以使用
  • defineClass()方法是用来将byte字节流解析成JVM能够识别的Class对象,通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象
  • defineClass()方法通常与findClass()方法一起使用,一般情况下,在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法并编写加载规则,取得要加载类的字节码转换成流,然后调用defineClass()方法生成类的Class对象
private Class<?> defineClass(String name, Resource res) throws IOException {long t0 = System.nanoTime();int i = name.lastIndexOf('.');URL url = res.getCodeSourceURL();if (i != -1) {String pkgname = name.substring(0, i);// Check if package already loaded.Manifest man = res.getManifest();definePackageInternal(pkgname, man, url);}// Now read the class bytes and define the classjava.nio.ByteBuffer bb = res.getByteBuffer();if (bb != null) {// Use (direct) ByteBuffer:CodeSigner[] signers = res.getCodeSigners();CodeSource cs = new CodeSource(url, signers);sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);return defineClass(name, bb, cs);} else {byte[] b = res.getBytes();// must read certificates AFTER reading bytes.CodeSigner[] signers = res.getCodeSigners();CodeSource cs = new CodeSource(url, signers);sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);return defineClass(name, b, 0, b.length, cs);}}
  • protected final void resolveClass(Class<?> c) :链接指定的一个Java类,使用该方法可以使用类的Class对象创建完成的同时也被解析,前面我们说链接阶段主要是对字节码进行验证,为类变量分配内存并设置初始值同时将字节码文件中的符号引用转换为直接引用
  • protected final Class<?> findLoadedClass(String name):查找名称为name的已经被加载过的类,返回结果为java.lang.Class类的实例,这个方法是final方法,无法被修改
  • private final ClassLoader parent :它也是一个ClassLoader的实例,这个字段表示的ClassLoader也称为这个ClassLoader的双亲,在类加载过程中,ClassLoader可能会将某些请求交予自己的双亲处理
SecureClassLoader与URLClassLoader
  • SecureClassLoader扩展了ClassLoader,新增了几个与使用相关的代码源和权限定义类验证的方法,一般不直接与此类打交道,更多与其子类URLClassLoader有所关联
  • ClassLoader是一个抽象类,很多方法是空没有实现,如findClass,findResource等,而URLClassLoader这个实现类为这些方法提供了具体实现,新增了URLClassPath类协助取得Class字节码流等功能,在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,这样就可以避免自己去编写findClassLoader()方法及其获取字节码流的方式,使自定义类更加简洁
ExtClassLoader与AppClassLoader
  • 这两个类继承URLClassLoader,是sun.misc.Launcher的静态内部类,sun.misc.Launcher主要被系统用于启动主应用程序,ExtClassLoader和AppClassLoader都由sun.misc.Launcher创建
  • ExtClassLoader没有重写loadClass()方法,AppClassLoader重写了loadClass方法,但最终调用还是父类的loadClass()方法,因此两个类都遵循双亲委派模式
Class.forName()与ClassLoader.loadClass()
  • Class.forName()是一个静态方法,最常用的Class.forName(String className)根据传入的类的全限定名返回一个Class对象,该方法在将Class文件加载到内存的同时,会执行类的初始化
  • ClassLoader.loadClass():是一个实例方法,需要一个ClassLoader对象来调用该方法,该方法将Class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化,该方法因为需要得到一个ClassLoader对象,所以可以根据需要指定使用哪个类加载器

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

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

相关文章

打开C语言常用的内存函数大门(三) —— memset()函数(内含讲解用法和模拟实现)

文章目录 1. 前言2. memset函数2.1 memset函数原型2.2 memset函数参数的介绍2.3 memset函数的使用演示 3. memset函数的模拟实现4. 总结 1. 前言 哈喽&#xff0c;我们又见面了。通过前面两个内存函数(memcpy、memmove函数)讲解的锤炼后&#xff0c;对如何解析一个自己从来没有…

V90PN伺服驱动器支持的标准报文介绍

1、V90 PN总线伺服通过FB285实现速度控制 V90 PN总线伺服通过FB285速度控制实现正弦位置轨迹运动(解析法和数值法对比测试)-CSDN博客文章浏览阅读448次。上面的位置函数有明确的解析函数&#xff0c;这里我们可以利用解析法求解其导数(微分),当然我们这里借助第三方数学软件求…

【LIN】STM32新能源汽车LIN通信实现过程

【LIN】STM32新能源汽车LIN通信实现过程 文章目录 前言一、软件二、接线图三、硬件原理图四、上位机五、PICO示波器串行解码1.软件中的LIN波特率设置-192002.PIC设置3.PIC串行解码 六.引用总结 前言 【电机控制】直流有刷电机、无刷电机汇总——持续更新 使用工具&#xff1a;…

opencv笔记(13)—— 停车场车位识别

一、所需数据介绍 car1.h5 是训练后保存的模型 class_directionary 是0&#xff0c;1的分类 二、图像数据预处理 对输入图片进行过滤&#xff1a; def select_rgb_white_yellow(self,image): #过滤掉背景lower np.uint8([120, 120, 120])upper np.uint8([255, 255, 255])#…

单片机原理及应用复习

单片机原理及应用 第二章 在AT89S52单片机中&#xff0c;如果采用6MHz晶振&#xff0c;一个机器周期为 2us 。 时钟周期Tocs1focs 机器周期 Tcy12focs 指令周期&#xff1a;一条指令所用的时间&#xff0c;单字和双字节指令一般为单机器周期和双机器周期。 AT89S5…

Unity2D横版摄像机跟随

在Unity2D横版游戏中&#xff0c;摄像机跟随是一个非常重要的功能。一个流畅的摄像机跟随系统可以让玩家更好地沉浸在游戏世界中。本文将介绍如何在Unity中实现2D横版摄像机跟随&#xff0c;并分享一些优化技巧。 一、准备工作 在开始实现摄像机跟随之前&#xff0c;请确保您…

chatgpt之api的调用问题

1.调用api过程中&#xff0c;出现如下报错内容 先写一个测试样例 import openaiopenai.api_key "OPEN_AI_KEY" openai.api_base"OPEN_AI_BASE_URL" # 是否需要base根据自己所在地区和key情况进行completion openai.ChatCompletion.create(model"g…

python对文本操作,生成可执行文件

.exe文件主要包含pingmianF.py文件和read_inp_auto.py文件 实现效果 代码 read_inp_auto.py #-*- coding: utf-8 -*- import re import sys import os import os.path import time import pingmianF from pingmianF import vector import numpy as np from tkinter import me…

61. UE5 RPG 实现敌人近战攻击技能和转向攻击

在前面&#xff0c;我们实现了敌人的AI系统&#xff0c;敌人可以根据自身的职业进行匹配对应的攻击方式。比如近战战士会靠近目标后进行攻击然后躲避目标的攻击接着进行攻击。我们实现了敌人的AI行为&#xff0c;但是现在还没有实现需要释放的技能&#xff0c;接下来&#xff0…

VBA字典与数组第十五讲:多行多列数组与同列数单行数组间的运算规则

《VBA数组与字典方案》教程&#xff08;10144533&#xff09;是我推出的第三套教程&#xff0c;目前已经是第二版修订了。这套教程定位于中级&#xff0c;字典是VBA的精华&#xff0c;我要求学员必学。7.1.3.9教程和手册掌握后&#xff0c;可以解决大多数工作中遇到的实际问题。…

开源模型应用落地-LangSmith试炼-入门初体验-监控和自动化(五)

一、前言 在许多应用程序中&#xff0c;特别是在大型语言模型(LLM)应用程序中&#xff0c;收集用户反馈以了解应用程序在实际场景中的表现是非常重要的。 LangSmith可以轻松地将用户反馈附加到跟踪数据中。通常最好提供一个简单的机制(如赞成和反对按钮)来收集用户对应用程序响…

Vue3中的常见组件通信之props和自定义事件

Vue3中的常见组件通信 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $refs4. 默认…

【计算机毕业设计】331基于微信小程序的家庭财务管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Linux——多线程(一)

一、线程的概念 1.1线程概念 教材中的概念&#xff1a; (有问题?) 线程是进程内部的一个执行分支&#xff0c;线程是CPU调度的基本单位 之前我们讲的进程&#xff1a; 加载到内存中的程序&#x…

数据库与缓存⼀致性⽅案

数据库与缓存⼀致性⽅案 1、背景2、数据⼀致性⽅案设计3、数据⼀致性⽅案流程图4、关键代码4.1、 处理数据⼀致性的消息队列⼊⼝4.2、数据⼀致性配置的常量信息 1、背景 现有的业务场景下&#xff0c;都会涉及到数据库以及缓存双写的问题&#xff0c;⽆论是先删除缓存&#xf…

claude3国内API接口对接

众所周知&#xff0c;由于地理位置原因&#xff0c;Claude3不对国内开放&#xff0c;而国内的镜像网站使用又贵的离谱&#xff01; 因此&#xff0c;团队萌生了一个想法&#xff1a;为什么不创建一个一站式的平台&#xff0c;让用户能够通过单一的接口与多个模型交流呢&#x…

壁纸动态-Mac电脑-4K超高清[po破]动态壁纸[解]Dynamic WallPaper 安装使用教程

Mac分享吧 文章目录 效果一、准备工作二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行调试1、打开软件&#xff0c;选择自己喜欢的壁纸2、调整设置&#xff0c;使多个壁…

OpenAI 再次刷新认知边界:GPT-4 颠覆语音助手市场,流畅度直逼真人互动?

前言 近日&#xff0c;美国人工智能研究公司 OpenAI 发布了其最新旗舰模型 GPT-4o&#xff0c;这一革命性的进展不仅标志着人工智能领域的新突破&#xff0c;更预示着即将步入一个全新的交互时代&#xff1f;GPT-4o 的发布&#xff0c;对于我们来说&#xff0c;意味着人工智能…

23.Labview中的数值类型讨论 ---- 位(bit)、字节(byte)、I8、U8、单双精度、复数

hello&#xff0c;大家好&#xff0c;本篇向大家介绍一个最常用但最容易让人忽略和最容易犯错的知识&#xff1a;数值。 “数值” 这个概念在Labview中被涉及的还是很多的&#xff0c;几乎任何一个程序都无可避免的会用到&#xff0c;但我相信大家绝大多数人对数值这个概念应用…

低代码开发平台(Low-code Development Platform)的模块组成部分

低代码开发平台&#xff08;Low-code Development Platform&#xff09;的模块组成部分主要包括以下几个方面&#xff1a; 低代码开发平台的模块组成部分可以按照包含系统、模块、菜单组织操作行为等维度进行详细阐述。以下是从这些方面对平台模块组成部分的说明&#xff1a; …