JVM类加载器详解

文章目录

  • 1.类与类加载器
  • 2.类加载器加载规则
  • 3.JVM 中内置的三个重要类加载器
    • 为什么 获取到 ClassLoader 为null就是 BootstrapClassLoader 加载的呢?
  • 4.自定义类加载器
    • 什么时候需要自定义类加载器
    • 代码示例
  • 5.双亲委派模式
    • 类与类加载器
    • 双亲委派模型
    • 双亲委派模型的执行流程
    • 双亲委派模型的好处
    • 打破双亲委派模型方法
  • 6.线程上下文类加载器


1.类与类加载器

类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远超类加载阶段。
对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,一个类加载器,都拥有一个独立的类名称空间。

这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。

这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括了使用instanceof关键字做对象所属关系判定等各种情况。

官方 API 文档的介绍:
类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。

每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

从上面的介绍可以看出:

  • 类加载器是一个负责加载类的对象,用于实现类加载过程中的加载这一步。
  • 每个 Java 类都有一个引用指向加载它的 ClassLoader。
  • 数组类不是通过 ClassLoader 创建的(数组类没有对应的二进制字节流),是由 JVM 直接生成的。

简单来说,类加载器的主要作用就是动态加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。 字节码可以是 Java 源程序(.java文件)经过 javac 编译得来,也可以是通过工具动态生成或者通过网络下载得来。

其实除了加载类之外,类加载器还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源。

2.类加载器加载规则

JVM 启动的时候,并不会一次性加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样对内存更加友好。

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

public abstract class ClassLoader {...private final ClassLoader parent;// 由这个类加载器加载的类。private final Vector<Class<?>> classes = new Vector<>();// 由VM调用,用此类加载器记录每个已加载类。void addClass(Class<?> c) {classes.addElement(c);}...
}

3.JVM 中内置的三个重要类加载器

JVM 中内置了三个重要的 ClassLoader:

  1. BootstrapClassLoader(启动类加载器)最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jar、resources.jar、charsets.jar等 jar 包和类)以及被 -Xbootclasspath参数指定的路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器):主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
  3. AppClassLoader(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。

🌈 拓展一下:
rt.jar:rt 代表“RunTime”,rt.jar是 Java 基础类库,包含 Java doc 里面看到的所有的类的类文件。也就是说,我们常用内置库 java.xxx.*都在里面,比如java.util.*、java.io.*、java.nio.*、java.lang.*、java.sql.*、java.math.*。
Java 9 引入了模块系统,并且略微更改了上述的类加载器。扩展类加载器被改名为平台类加载器(platform class loader)。Java SE 中除了少数几个关键模块,比如说 java.base 是由启动类加载器加载之外,其他的模块均由平台类加载器所加载。

除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行拓展,以满足自己的特殊需求。就比如说,我们可以对 Java 类的字节码( .class 文件)进行加密,加载时再利用自定义的类加载器对其解密。

除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。

每个 ClassLoader 可以通过getParent()获取其父 ClassLoader,如果获取到 ClassLoader 为null的话,那么该类是通过 BootstrapClassLoader 加载的。

public abstract class ClassLoader {...// 父加载器private final ClassLoader parent;@CallerSensitivepublic final ClassLoader getParent() {//...}...
}

为什么 获取到 ClassLoader 为null就是 BootstrapClassLoader 加载的呢?

这是因为BootstrapClassLoader 由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。

4.自定义类加载器

除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader抽象类。

ClassLoader 类有两个关键的方法:

  • protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,实现了双亲委派机制 。name 为类的二进制名称,resolve 如果为 true,在加载时调用 resolveClass(Class<?> c) 方法解析该类。
  • protected Class findClass(String name):根据类的二进制名称来查找类,默认实现是空方法。

注意:如果我们不想打破双亲委派模型,就重写 ClassLoader 类中的 findClass() 方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass() 方法

什么时候需要自定义类加载器

  • 想加载非 classpath 随意路径中的类文件。
  • 都是通过接口来使用实现、希望解耦时,常用在框架设计。
  • 这些类希望予以隔离,不同应用的同名类都可以加载,不会发生冲突,常见于 tomcat 容器。

步骤:

  1. 继承 ClassLoader 父类
  2. 要遵从双亲委派机制,重写 findClass 方法
    注意不是重写 loadClass 方法,否则不会走双亲委派机制
  3. 读取类文件的字节码
  4. 调用父类的 defineClass 方法来加载类
  5. 使用者调用该类加载器的 loadClass 方法

代码示例

public class F {//通过是否运行静态代码块观察是否被加载并初始化static {System.out.println("bootstrap F init");}
}

自定义类加载器

class MyClassLoader extends ClassLoader {@Override // name 就是类名称protected Class<?> findClass(String name) throws ClassNotFoundException {//F.class位置String path = "D:\\java\\jvm\\out\\production\\jvm\\" + name + ".class";try {ByteArrayOutputStream os = new ByteArrayOutputStream();Files.copy(Paths.get(path), os);// 得到字节数组byte[] bytes = os.toByteArray();// byte[] -> *.classreturn defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {e.printStackTrace();throw new ClassNotFoundException("类文件未找到", e);}}@Overrideprotected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 打印加载类的类加载器System.out.println("Loading class " + name + " with " + this);return super.loadClass(name, resolve);}
}

使用自定义类加载器加载F类

public class Load {public static void main(String[] args) throws Exception {MyClassLoader classLoader = new MyClassLoader();Class<?> c1 = classLoader.loadClass("F");Class<?> c2 = classLoader.loadClass("F");System.out.println(c1 == c2);MyClassLoader classLoader2 = new MyClassLoader();Class<?> c3 = classLoader2.loadClass("F");System.out.println(c1 == c3);System.out.println("c1: " + c1.getClassLoader());System.out.println("c2: " + c2.getClassLoader());System.out.println("c3: " + c3.getClassLoader());c1.newInstance();}
}

输出

Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@12bb4df8
Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@12bb4df8
true
Loading class F with cn.itcast.jvm.t3.load.MyClassLoader@4cc77c2e
true
c1: sun.misc.Launcher$AppClassLoader@18b4aac2
c2: sun.misc.Launcher$AppClassLoader@18b4aac2
c3: sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrap F init

5.双亲委派模式

类与类加载器

站在Java虚拟机的角度来看,只存在两种不同的类加载器:

  • 一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C++语言实现[1],是虚拟机自身的一部分;
  • 另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。

站在Java开发人员的角度来看,类加载器就应当划分得更细致一些:
JDK 9之前的Java应用都是由启动、扩展、应用程序类加载器互相配合来完成加载的,如果用户认为有必要,还可以加入自定义的类加载器来进行拓展,典型的如增加除了磁盘位置之外的Class文件来源,或者通过类加载器实现类的隔离、重载等功能。

类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载呢?这就需要提到双亲委派模型了。

  • ClassLoader 类使用委托模型来搜索类和资源。
  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。
  • ClassLoader 实例会在试图亲自查找类或资源之前,将搜索类或资源的任务委托给其父类加载器。

双亲委派模型

下图展示的各种类加载器之间的层次关系被称为类加载器的“双亲委派模型(Parents Delegation Model)”。
在这里插入图片描述
双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。不过这里类加载器之间的父子关系一般不是以继承(Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。

注意⚠️:类加载器的双亲委派模型在JDK 1.2时期被引入,并被广泛应用于此后几乎所有的Java程序中,但它并不是一个具有强制性约束力的模型,而是Java设计者们推荐给开发者的一种类加载器实现的最佳实践。

在面向对象编程中,有一条非常经典的设计原则:组合优于继承,多用组合少用继承

双亲委派模型的执行流程

java.lang.ClassLoader#loadClass(java.lang.String, boolean)

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 1. 检查该类是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {//如果 c 为 null,则说明该类没有被加载过long t0 = System.nanoTime();try {if (parent != null) {// 2. 有上级的话,委派上级 loadClass来加载该类c = parent.loadClass(name, false);} else {// 3. 如果没有上级了(ExtClassLoader),则委派BootstrapClassLoaderc = 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();// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载c = findClass(name);// this is the defining class loader; record the stats// 5. 记录耗时sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}
}

结合上面的源码,简单总结一下双亲委派模型的执行流程:

  • 如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载(每个父类加载器都会走一遍这个流程)。
  • 类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器 loadClass()方法来加载类)。这样的话,所有的请求最终都会传送到顶层的启动类加载器 BootstrapClassLoader 中。
  • 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载(调用自己的 findClass() 方法来加载类)。
  • 如果子类加载器也无法加载这个类,那么它会抛出一个 ClassNotFoundException 异常。

双亲委派模型的好处

双亲委派模型是 Java 类加载机制的重要组成部分,它通过委派父加载器优先加载类的方式,实现了两个关键的安全目标:避免类的重复加载和防止核心 API 被篡改

JVM 区分不同类的依据是类名加上加载该类的类加载器,即使类名相同,如果由不同的类加载器加载,也会被视为不同的类。 双亲委派模型确保核心类总是由 BootstrapClassLoader 加载,保证了核心类的唯一性。

例如,当应用程序尝试加载 java.lang.Object 时,AppClassLoader 会首先将请求委派给 ExtClassLoader,ExtClassLoader 再委派给 BootstrapClassLoader。BootstrapClassLoader 会在 JRE 核心类库中找到并加载 java.lang.Object,从而保证应用程序使用的是 JRE 提供的标准版本。

同时即使攻击者绕过了双亲委派模型,Java 仍然具备更底层的安全机制来保护核心类库。ClassLoader 的 preDefineClass 方法会在定义类之前进行类名校验。任何以 “java.” 开头的类名都会触发 SecurityException,阻止恶意代码定义或加载伪造的核心类。

打破双亲委派模型方法

重写 loadClass() 方法打破双亲委派模型,
原因:类加载器在进行类加载的时候,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成(调用父加载器 loadClass()方法来加载类)。

重写 loadClass()方法之后,我们就可以改变传统双亲委派模型的执行流程。例如,子类加载器可以在委派给父类加载器之前,先自己尝试加载这个类,或者在父类加载器返回之后,再尝试从其他地方加载这个类。具体的规则由我们自己实现,根据项目需求定制化。

我们比较熟悉的 Tomcat 服务器为了能够优先加载 Web 应用目录下的类,然后再加载其他目录下的类,就自定义了类加载器 WebAppClassLoader 来打破双亲委托机制。这也是 Tomcat 下 Web 应用之间的类实现隔离的具体原理。

Tomcat 的类加载器的层次结构如下:
在这里插入图片描述
从图中的委派关系中可以看出:

  • CommonClassLoader作为 CatalinaClassLoader 和 SharedClassLoader 的父加载器。CommonClassLoader 能加载的类都可以被 CatalinaClassLoader 和 SharedClassLoader 使用。因此,CommonClassLoader 是为了实现公共类库(可以被所有 Web 应用和 Tomcat 内部组件使用的类库)的共享和隔离。
  • CatalinaClassLoader 和 SharedClassLoader 能加载的类则与对方相互隔离。CatalinaClassLoader 用于加载 Tomcat 自身的类,为了隔离 Tomcat 本身的类和 Web 应用的类。SharedClassLoader 作为 WebAppClassLoader 的父加载器,专门来加载 Web 应用之间共享的类比如 Spring、Mybatis。
  • 每个 Web 应用都会创建一个单独的 WebAppClassLoader,并在启动 Web 应用的线程里设置线程上下文类加载器为 WebAppClassLoader。各个 WebAppClassLoader 实例之间相互隔离,进而实现 Web 应用之间的类隔。

单纯依靠自定义类加载器没办法满足某些场景的要求,例如,有些情况下,高层的类加载器需要加载低层的加载器才能加载的类。

比如,假设我们的项目中有 Spring 的 jar 包,由于其是 Web 应用之间共享的,因此会由 SharedClassLoader 加载(Web 服务器是 Tomcat)。

我们项目中有一些用到了 Spring 的业务类,比如实现了 Spring 提供的接口、用到了 Spring 提供的注解。所以,加载 Spring 的类加载器(也就是 SharedClassLoader)也会用来加载这些业务类。

但是业务类在 Web 应用目录下,不在 SharedClassLoader 的加载路径下,所以 SharedClassLoader 无法找到业务类,也就无法加载它们。

如何解决这个问题呢? 这个时候就需要用到 线程上下文类加载器(ThreadContextClassLoader) 了。

6.线程上下文类加载器

  • 拿 Spring 这个例子来说,当 Spring 需要加载业务类的时候,它不是用自己的类加载器,而是用当前线程的上下文类加载器。
  • 因为每个 Web 应用都会创建一个单独的 WebAppClassLoader,并在启动 Web 应用的线程里设置线程上下文类加载器为 WebAppClassLoader。
  • 这样就可以让高层的类加载器(SharedClassLoader)借助子类加载器( WebAppClassLoader)来加载业务类,破坏了 Java 的类加载委托机制,让应用逆向使用类加载器。

线程上下文类加载器的原理是将一个类加载器保存在线程私有数据里,跟线程绑定,然后在需要的时候取出来使用。这个类加载器通常是由应用程序或者容器(如 Tomcat)设置的
Java.lang.Thread 中的getContextClassLoader()和 setContextClassLoader(ClassLoader cl)分别用来获取和设置线程的上下文类加载器。如果没有通过setContextClassLoader(ClassLoader cl)进行设置的话,线程将继承其父线程的上下文类加载器。
Spring 获取线程线程上下文类加载器的代码如下:

cl = Thread.currentThread().getContextClassLoader();

我们在使用 JDBC 时,都需要加载 Driver 驱动,不知道你注意到没有,不写Class.forName("com.mysql.jdbc.Driver"),也是可以让 com.mysql.jdbc.Driver 正确加载,原因:
java.sql.DriverManager

public class DriverManager {// 注册驱动的集合private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();// 初始化驱动static {loadInitialDrivers();println("JDBC DriverManager initialized");}
}

先看看 DriverManager 的类加载器:

System.out.println(DriverManager.class.getClassLoader());

打印 null,表示它的类加载器是 Bootstrap ClassLoader,会到 JAVA_HOME/jre/lib 下搜索类,但 JAVA_HOME/jre/lib 下显然没有 mysql-connector-java-5.1.47.jar 包,这样问题来了,在DriverManager 的静态代码块中,怎么能正确加载 com.mysql.jdbc.Driver 呢?

继续看 loadInitialDrivers() 方法:

private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}// 1.使用 ServiceLoader 机制加载驱动,即 SPIAccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);// 2.使用 jdbc.drivers 定义的驱动名加载驱动if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);// 这里的 ClassLoader.getSystemClassLoader() 就是应用程序类加载器Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}
}

先看 2. 发现它最后是使用 Class.forName 完成类的加载和初始化,关联的是应用程序类加载器,因此可以顺利完成类加载
再看 1. 它就是大名鼎鼎的 Service Provider Interface (SPI)
约定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名为文件,文件内容是实现类名称
在这里插入图片描述
这样就可以使用以下代码

ServiceLoader<接口类型> allImpls = ServiceLoader.load(接口类型.class);
Iterator<接口类型> iter = allImpls.iterator();
while(iter.hasNext()) {iter.next();
}

来得到实现类,体现的是【面向接口编程+解耦】的思想,在下面一些框架中都运用了此思想:

  • JDBC
  • Servlet 初始化器
  • Spring 容器
  • Dubbo(对 SPI 进行了扩展)

接着看 ServiceLoader.load 方法:

public static <S> ServiceLoader<S> load(Class<S> service) {// 获取线程上下文类加载器(其实是应用程序类加载器)ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);
}

线程上下文类加载器是当前线程使用的类加载器,默认就是应用程序类加载器,它内部又是由Class.forName 调用了线程上下文类加载器完成类加载,具体代码在 ServiceLoader 的内部类LazyIterator 中:
java.util.ServiceLoader.LazyIterator#nextService

private S nextService() {if (!hasNextService())throw new NoSuchElementException();String cn = nextName;nextName = null;Class<?> c = null;try {c = Class.forName(cn, false, loader);} catch (ClassNotFoundException x) {fail(service,"Provider " + cn + " not found");}if (!service.isAssignableFrom(c)) {fail(service,"Provider " + cn  + " not a subtype");}try {S p = service.cast(c.newInstance());providers.put(cn, p);return p;} catch (Throwable x) {fail(service,"Provider " + cn + " could not be instantiated",x);}throw new Error();          // This cannot happen
}

相关文章:
JVM内存结构

  • JVM内存结构笔记01-运行时数据区域
  • JVM内存结构笔记02-堆
  • JVM内存结构笔记03-方法区
  • JVM内存结构笔记04-字符串常量池
  • JVM内存结构笔记05-直接内存
  • JVM内存结构笔记06-HotSpot虚拟机对象探秘
  • JVM中常量池和运行时常量池、字符串常量池三者之间的关系

JVM垃圾回收

  • JVM垃圾回收笔记01-垃圾回收算法
  • JVM垃圾回收笔记02-垃圾回收器

JVM类加载与字节码

  • JVM类文件结构详解
  • JVM类加载过程详解
  • JVM类加载器详解

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

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

相关文章

Chapters 15 16:What Is Architecture?Independence_《clean architecture》notes

What Is Architecture?&Independence **Chapter 15: What Is Architecture?****Key Concepts**:**Code Example: Layered Architecture**: **Chapter 16: Independence****Key Concepts**:**Code Example: Dependency Inversion & Interfaces**: **Combined Example:…

【SPP】RFCOMM 层在SPP中互操作性要求深度解析

蓝牙串口协议&#xff08;SPP&#xff09;通过 RFCOMM 协议实现 RS232 串口仿真&#xff0c;其互操作性是设备互联的关键。本文基于蓝牙核心规范&#xff0c;深度解析 RFCOMM 层的能力矩阵、信号处理、流控机制及实战开发&#xff0c;结合状态机、流程图和代码示例&#xff0c;…

阻塞式IO与非阻塞IO的区别

阻塞式IO与非阻塞IO的区别 1. 阻塞式IO (Blocking I/O) 定义 当程序发起一个I/O操作&#xff08;如读取文件、网络数据&#xff09;时&#xff0c;进程会被挂起&#xff08;阻塞&#xff09;&#xff0c;直到操作完成或超时才会继续执行后续代码。在此期间&#xff0c;程序无法…

Gossip协议:分布式系统中的“八卦”传播艺术

目录 一、 什么是Gossip协议&#xff1f;二、 Gossip协议的应用 &#x1f4a1;三、 Gossip协议消息传播模式详解 &#x1f4da;四、 Gossip协议的优缺点五、 总结&#xff1a; &#x1f31f;我的其他文章也讲解的比较有趣&#x1f601;&#xff0c;如果喜欢博主的讲解方式&…

【C++初阶】----模板初阶

1.泛型函数 泛型编程&#xff1a;编写与类型无关的通用代码&#xff0c;是代码复用的一种手段。模板是泛型编程的基础。 2.函数模板 2.1函数模板的概念 函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型…

git-- github的使用--账户和本地连接

以下指令在git 执行bash 流程&#xff1a;先看有没有密钥&#xff1b; 没有的话&#xff0c;在电脑生成密钥对&#xff0c;公钥复制到github&#xff1b; 要想使用https&#xff0c;配置令牌&#xff0c;注意令牌有期限问题&#xff0c;连接不了有可能是期限问题 一个电脑对…

OTN(Optical Transport Network)详解

OTN&#xff08;光传送网&#xff09;是一种基于**波分复用&#xff08;WDM&#xff09;**的大容量光传输技术&#xff0c;结合了SDH的运维管理优势和WDM的高带宽特性&#xff0c;广泛应用于骨干网、城域核心层及数据中心互联&#xff08;DCI&#xff09;。 1. OTN 的基本概念 …

Python 中列表(List)、元组(Tuple)、集合(Set)和字典(Dict)四大数据结构的完整对比

以下是 Python 中列表&#xff08;List&#xff09;、元组&#xff08;Tuple&#xff09;、集合&#xff08;Set&#xff09;和字典&#xff08;Dict&#xff09;四大数据结构的完整对比分析&#xff0c;结合了核心特性、操作方式和应用场景的深度总结&#xff1a; 一、核心特性…

Angular由一个bug说起之十五:自定义基于Overlay的Tooltip

背景 工具提示&#xff08;tooltip&#xff09;是一个常见的 UI 组件&#xff0c;用于在用户与页面元素交互时提供额外的信息。由于angular/material/tooltip的matTooltip只能显示纯文本&#xff0c;所以我们可以通过自定义Directive来实现一个灵活且功能丰富的tooltip Overlay…

软件工程面试题(十五)

1、servlet 创建过程以及ruquest,response,session的生命周期? Servlet的创建过程: 第一步 public class AAA extends HttpServlet{ 实现对应的doxxx方法 } 第二步: 在web.xml中配置 <servlet> <servlet-name></servlet-name> <servlet-c…

搭建QNX Software Center的Docker环境

背景 本人使用 Ubuntu Server 22.04 服务器&#xff0c;所以没有图形界面&#xff0c;而 QNX Software Center 需要图形界面。为了保证服务器环境的整理&#xff0c;计划使用Docker部署QNX Software Center 一瓶安装图形界面。本方既是实现方案的记录。 资源 Dockerfile&…

C#/.NET/.NET Core技术前沿周刊 | 第 31 期(2025年3.17-3.23)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…

粘包问题解决方案

粘包问题详解&#xff1a;TCP协议中的常见问题及Go语言解决方案 一、什么是粘包问题&#xff1f; 粘包问题是指在TCP通信中&#xff0c;发送方发送的多个独立消息在接收方被合并成一个消息接收的现象。换句话说&#xff0c;发送方发送的多条消息在接收方被“粘”在一起&#…

vue:突然发现onok无法使用

const that this;this.$confirm({title: "修改商品提示",content: "如果当前商品存在于商品活动库&#xff0c;则在商品活动库的状态会下架",onOk: function () {that.submitForm();}}); 突然发现 this.$confirm无法进入onok 最终发现是主题冲突&#x…

redis hashtable 的sizemask理解

在 Redis 的哈希表实现中&#xff0c;index hash & dict->ht[0].sizemask 是计算键值对应存储位置的核心操作。这个操作看起来简单&#xff0c;但背后涉及哈希表的内存布局和性能优化策略。我们通过以下步骤逐步解析其原理&#xff1a; 一、哈希表的设计目标 快速定位…

Ruby 命令行选项

Ruby 命令行选项 概述 Ruby 是一种广泛使用的编程语言,它拥有强大的命令行工具,可以帮助开发者进行各种任务。了解 Ruby 的命令行选项对于提高开发效率至关重要。本文将详细介绍 Ruby 的常用命令行选项,帮助开发者更好地利用 Ruby 的命令行功能。 Ruby 命令行选项概述 R…

【STM32】WDG看门狗(学习笔记)

学习来源----->江协科技STM32 WDG简介 WDG&#xff08;Watchdog&#xff09;看门狗看门狗可以监控程序的运行状态&#xff0c;当程序因为设计漏洞、硬件故障、电磁干扰等原因&#xff0c;出现卡死或跑飞现象时&#xff0c;看门狗能及时复位程序&#xff0c;避免程序陷入长…

Java 数据库连接池

HikariCP 老外开源的。 Spring Boot 2 之后默认选择的连接池。 号称性能最快的数据库连接池。 为什么性能好呢&#xff1f; ● 字节码级别的优化-尽量的利用 JIT 的内联手段 ● 字节码级别的优化-利用更容易被 JVM 优化的指令 ● 代码级别的优化-利用改造后的 FastList 代替…

Spring Boot中@Valid 与 @Validated 注解的详解

Spring Boot中Valid 与 Validated 注解的详解 引言Valid注解功能介绍使用场景代码样例 Validated注解功能介绍使用场景代码样例 Valid与Validated的区别结论 引言 在Spring Boot应用中&#xff0c;参数校验是确保数据完整性和一致性的重要手段。Valid和Validated注解是Spring …

C++搜索

功能扩展说明&#xff1a; 图类封装&#xff1a;将图数据结构封装为类&#xff0c;提高代码复用性 最短路径查找&#xff1a;基于BFS实现未加权图的最短路径查找 路径重构&#xff1a;通过parent数组回溯构建完整路径 异常处理&#xff1a;当路径不存在时返回空向量 复杂度分析…