一文理类加载相关知识:类加载器、双亲委派、SPI

思维导图

在这里插入图片描述

类加载的时机

在这里插入图片描述

类加载的流程

类从被加载到内存中开始,直到被从内存中卸载为止,它的整个生命周期包括:验证、准备、解析、初始化、使用和卸载7 个阶段。
其中验证、准备、解析 3 个部分统称为连接(Linking)
在这里插入图片描述

1.加载(重点)

类加载过程的第一步,主要完成下面 3 件事情:

  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

加载阶段(准确地说,是加载阶段中获取类的二进制字节流的动作)可以使用系统提供的类加载器(ClassLoader)来完成,也可以由用户自定义的类加载器完成,开发人员可以通过定义自己的类加载器去控制字节流的获取方式。

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义,虚拟机并未规定此区域的具体数据结构。然后在java堆中实例化一个java.lang.Class类的对象,这个对象作为程序访问方法区中的这些类型数据的外部接口。

2.验证

验证是链接阶段的第一步,这一步主要的目的是确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身安全。
验证阶段主要包括四个检验过程:文件格式验证、元数据验证、字节码验证和符号引用验证。

3.准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个阶段中有两个容易产生混淆的知识点,首先是这时候进行内存分配的仅包括类变量(static 修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在java堆中

4.解析

解析阶段是虚拟机常量池内的符号引用替换为直接引用的过程。

  • 符号引用:符号引用是一组符号来描述所引用的目标对象,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标对象并不一定已经加载到内存中。
  • 直接引用:直接引用可以是直接指向目标对象的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机内存布局实现相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。

5.初始化

类的初始化阶段是类加载过程的最后一步,在准备阶段,类变量已赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器()方法的过程。

类加载器

类加载器主要分为四类:

BootStrap ClassLoader:启动类加载器,C++实现的,是Java类加载层次中最顶层的类加载器(JVM启动后初始化的),负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等;

ExtensionClassLoader:扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。该加载器是有java实现的,由Bootstrploader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrp loader;

AppClassLoader:系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。

CustomLoader:自定义类加载器,负责加载指定的目录和文件

双亲委派

类加载器在加载类时,会先委托父类加载器去加载该类,如果父类加载器无法加载才会尝试自己加载。

当一个ClassLoader实例需要加载某个类时,它会先检查父类加载器(一直检查到Bootstrap ClassLoader)是否已经加载过该类,如果父类加载器已经加载该类则直接返回该类对象。然后由上至下依次加载类,首先由最顶层的类加载器BootstrapClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给AppClassLoader进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException异常。

JVM在判定两个Class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。

双亲委派的优点

  1. 可以避免重复加载,当父亲已经加载了该类的时候,就没有必要让子ClassLoader再加载一次。
  2. 避免Java核心类不被随意替换

打破双亲委派

在实际的应用中双亲委派解决了java 基础类统一加载的问题,但是也存在着问题。jdk中的基础类作为用户api被调用,但是也存在调用用户的代码的情况,典型的如SPI。

Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是作为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码经常需要加载具体的实现类。

那么问题来了,SPI的接口是Java核心库的一部分,是由启动类加载器(Bootstrap Classloader)加载的,而SPI的实现类是由系统类加载器(App ClassLoader)来加载的。启动类加载器是无法找到 SPI 的实现类的,因为依照双亲委派模型,BootstrapClassloader无法委派AppClassLoader来加载类

(1)线程上下文类加载器

为解决上述问题,引入了线程上下文类加载器(Thread Context ClassLoader),线程上下文类加载器可以通过java.lang.Thread 类的setContextClassLoader方法进行设置。默认情况下为系统类加载器(App ClassLoader)
通过线程上下文类加载器,父类即可打破双亲委派模型,委托子类加载器实现类的加载。当父类无法加载某个类时,就可以委托线程上下文类加载器加载对应的类。

(2)自定义类加载器覆写loadClass()

自定义加载器,需要继承 ClassLoader 。如果不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法。

SPI

SPI(服务提供接口) ,全称为 Service Provider Interface,可以理解为调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。SPI接口一般在核心库里,由BootStrap ClassLoader加载。

SPI是一种服务发现机制。SPI约定在 Classpath 下的 META-INF/services/ 目录里创建一个以服务接口命名的文件(如:java.sql.Driver),然后文件里面记录的是此 jar 包提供的接口具体实现类的全限定名(如:Mysql中提供的 com.mysql.cj.jdbc.Driver)。
在加载接口的实现类时,通过在查找ClassPath路径下的META-INF/services文件夹中存有实现类类名的文件,并实例化文件所定义的实现类,来实例化某个接口。

SPI 通过 ServiceLoader.load() 去完成上述的实例化META-INF/services中的类。ServiceLoader.load() 会通过 线程上下文类加载器(默认为App Loader)打破双亲委派,委子类类加载器去加载实现类。

SPI的主要流程:约定一个目录,调用ServiceLoader.load()根据接口名去那个目录找到文件,文件解析得到实现类的全限定名,然后循环加载实现类和创建其实例。

图片来源
在这里插入图片描述

Java SPI的缺点

Java SPI 无法按需加载实现类:Java SPI 在查找扩展实现类的时候遍历 SPI 的配置文件并且将实现类全部实例化,假设一个实现类初始化过程比较消耗资源且耗时,但是你的代码里面又用不上它,这就产生了资源的浪费。

推荐阅读:

  • 推荐:三歪问我Dubbo的SPI机制是啥?(带有ServiceLoader的源码分析)
  • Java SPI详解
  • 推荐:深入理解SPI机制

总结

类的加载过程基本如下图:
在这里插入图片描述

  1. 大部分类都依赖双亲委派模型进行加载;
  2. 以下情况会破坏双亲委派模型:
    (1)自定义类加载器覆写了loadClass()方法
    (2)父类加载器需要使用由子类加载器加载的类,此时父类加载器会使用线程上下文加载器,去委托子类加载器去加载相应的类
  3. 线程上下文类加载器的适用场景:
    (1)当高层提供了统一接口让低层去实现,同时又要是在高层加载(或实例化)低层的类时,必须通过线程上下文类加载器来帮助高层的ClassLoader找到并加载该类。
    (2)当使用本类托管类加载,然而加载本类的ClassLoader未知时,为了隔离不同的调用者,可以取调用者各自的线程上下文类加载器代为托管。

参考

  • 推荐:【JVM】浅谈双亲委派和破坏双亲委派
  • 推荐:详细jvm-类加载机制
  • 推荐:Java 类加载器
  • 推荐:深入理解SPI机制
  • 真正理解线程上下文类加载器(多案例分析)
  • 自定义类加载器:从网上加载class到内存、实例化调用其中的方法
  • jvm(1)类的加载(三)(线程上下文加载器)
  • 类加载过程

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

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

相关文章

可以搜python编程答案的软件_python实现百万答题自动百度搜索答案

用python搭建百万答题、自动百度搜索答案。 使用平台 windows7 python3.6 MIX2手机 代码原理 手机屏幕内容同步到pc端 对问题截图 对截图文字分析 用浏览器自动搜索文本 使用教程 1、使用Airdroid 将手机屏幕显示在电脑屏幕上。也可使用360手机助手实现。不涉及任何代码。实现效…

intellij idea设置主题、字体样式和背景色

转自&#xff1a; https://blog.csdn.net/fanrenxiang/article/details/80598895 点击这里查看 <intellij idea使用教程汇总篇> 引言&#xff1a;所谓工欲善其事必先利其器&#xff0c;idea就是这样的利器&#xff0c;刚装好的intellij idea主题样式是白的&#xff0c;…

MySQL优化(四):count()

count()不同写法的区别 COUNT(字段名)&#xff1a;返回SELECT语句检索的行中值不为NULL的行数 COUNT(1)&#xff1a;表示的是直接查询符合条件的数据库表的行数&#xff08;会包含值为NULL的行数&#xff09;。其中1指的是表中的第一个字段&#xff0c;如有表 table(id, colu…

图像sobel梯度详细计算过程_数字图像处理(第十章)

点、线、边缘检测背景知识。书中主要介绍了图像的一阶导数与二阶导数&#xff0c;这个之前的文章中有过介绍这里在复习一遍。对于函数 ,对于点 在x方向的一阶偏导为&#xff1a;,二阶偏导为&#xff1a;之后书中总结了一阶导与二阶导对于图像求取边缘的结论&#xff1a;孤立点检…

idea部署maven+javaweb项目到jboss

小编习惯使用eclipse对jboss跑的项目部署,第一次使用idea进行jboss部署项目,遇到很多问题,做此文章以帮助更多人. 图中涂鸦的是项目名,对应上自己的项目名即可 1.导入项目,这一步不多说 2.配置项目: a>点击file-->Project-Stucture-->Project 3.配置Modules 配置…

Java8-本地缓存

转载自 Java8-本地缓存这里我将会给大家演示用ConcurrentHashMap类和lambda表达式实现一个本地缓存。因为Map有一个新的方法可以在key为Null的时候自动计算一个新的value值。非常完美的实现cache。来看下代码&#xff1a;12345678910111213141516publicstatic void main(String…

Integer和Int的比较,谈谈拆卸和装箱

示例代码 public static void main(String[] args) {Integer a new Integer(10111);int b 10111;boolean equal1 a b;//自动拆箱&#xff0c;xxxValue()boolean equal2 a.equals(b);//自动装箱, valueOf()System.out.println(equal1);System.out.println(equal2); }反编译…

python调用webservice接口实例_python调用webservice接口的实现

使用suds这个第三方模块 from suds.client import Client url http://ip:port/?wsdl cilentClient(url) print cilent 查看webservice接口的具体信息&#xff1a; 调用接口方法&#xff0c;通常 client.service.methodname 实际测试过程中遇到的坑&#xff1a; 1、tns 值为Lo…

idea2021部署maven+javaweb项目到jboss(diy)

【README】 我为什么要写这个文章&#xff0c;看了这位老哥的博文 https://blog.csdn.net/PacosonSWJTU/article/details/118074604 部署成功了&#xff0c;很感谢&#xff0c;所以也想照做一下&#xff1b; 【1】创建web项目module &#xff08;Project02 是一个空项目&…

Java对象内存结构

转载自 Java对象内存结构学C/C出身的我&#xff0c;对Java有一点非常困惑&#xff0c;那就是缺乏计算对象占用内存大小的机制。而在C中就可以通过sizeof运算符来获得基本类型以及类实例的大小。C和C中的这个操作符对于指针运算、内存拷贝和IO操作都非常有用。 Java中并没有一个…

Java版大顶堆的实现

堆的概念 堆是一棵完全二叉树&#xff0c;一般使用数组来存储。通俗来讲堆其实就是利用数组来维护一个完全二叉树。 按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆&#xff1a;堆的每个结点的值都大于或等于其左右孩子结点的值 小顶堆&#xff1a;堆的每个结点的值都小于或…

Java 8新特性探究(二)深入解析默认方法

转载自 Java 8新特性探究&#xff08;二&#xff09;深入解析默认方法 什么是默认方法&#xff0c;为什么要有默认方法 简单说&#xff0c;就是接口可以有实现方法&#xff0c;而且不需要实现类去实现其方法。只需在方法名前面加个default关键字即可。 为什么要有这个特性&am…

把本地库推送到github远程库

【1】 github上创建远程库 注意 &#xff0c;远程库的名字要与本地库相同 【2】新建github远程库别名origin 【3】 代码提交 git add ./* &#xff1a; 把修改内容添加到暂存区 &#xff1b; git commit -m msg &#xff1a; 提交暂存区的修改内容到本地库&#xff1b; g…

react antd confirm content list_React造轮系列:对话框组件 - Dialog 思路

React造轮系列&#xff1a;对话框组件 - Dialog 思路对话框一般是我们点击按钮弹出的这么一个东西&#xff0c;主要类型有 Alter, Confirm 及 Modal, Modal 一般带有半透明的黑色背景。当然外观可参考 AntD 或者 Framework 等。确定 APIAPI 方面主要还是要参考同行&#xff0c;…

Spring IOC 和 AOP 概览

IOC&#xff08;控制反转&#xff09; IoC&#xff08;Inversion of Control&#xff0c;控制倒转&#xff09;。所谓IoC&#xff0c;对于spring框架来说&#xff0c;就是由spring来负责控制对象的生命周期和对象间的关系。 在没有IOC时&#xff0c;我们通过new 等关键字等方…

Java 并发实践 — ConcurrentHashMap 与 CAS

转载自 Java 并发实践 — ConcurrentHashMap 与 CAS最近在做接口限流时涉及到了一个有意思问题&#xff0c;牵扯出了关于concurrentHashMap的一些用法&#xff0c;以及CAS的一些概念。限流算法很多&#xff0c;我主要就以最简单的计数器法来做引。先抽象化一下需求&#xff1a;…

git rebase命令(转)

转自&#xff1a; https://www.yiibai.com/git/git_rebase.html git rebase命令在另一个分支基础之上重新应用&#xff0c;用于把一个分支的修改合并到当前分支。 使用语法 git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>][<u…

python tkinter计算器实例_python -Tkinter 实现一个小计算器功能

原博文 2017-03-25 22:08 − 文章来源&#xff1a;http://www.cnblogs.com/Skyyj/p/6618739.html 本代码是基于python 2.7的 如果是对于python3.X 则需要将 tkinter 改为Tkinter 将tkMessagebox&... 相关推荐 2019-12-10 15:59 − python GUI编程(Tkinter) Python 提供了多…

Spring IOC 容器启动、Bean生命周期详解

前言 在Spring IOC 和 AOP 概览中&#xff0c;简要介绍了IOC容器和AOP&#xff0c;没有深入IOC容器Bean的实例化&#xff0c;此文承接上文深入分析Bean的实例化过程、生命周期。 Spring IOC的过程 Spring的IoC容器在实现控制反转和依赖注入的过程中,可以划分为两个阶段: 容…

java 8 Lambda 表达式(副作用)

【1】转自&#xff1a; https://www.cnblogs.com/linlinismine/p/9283532.html 早在2014年oracle发布了jdk 8,在里面增加了lambda模块。于是java程序员们又多了一种新的编程方式&#xff1a;函数式编程&#xff0c;也就是lambda表达式。我自己用lambda表达式也差不多快4年了&am…