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

jvm的类加载

文章目录

    • 概要
    • 加载
      • 类加载器分类
      • 双亲委派模型
      • 自定义加载器
    • 验证
    • 准备
    • 解析
    • 初始化
      • <cinit>与<init>

概要

jvm运行时的整体结构如下
在这里插入图片描述
一个Car类,类跟Car对象的转换过程如下:
在这里插入图片描述

  • 加载后的class类信息存放于方法区;
  • ClassLoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定;
  • 如果调用构造器实例化对象,则该对象存放在堆区;

其中类的加载总体流程如下:
在这里插入图片描述

加载

类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

加载是类加载的第一个阶段。有两种时机会触发类加载

  • 预加载
    虚拟机启动时加载,加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面的内容是程序运行时非常常
    常用到的,像java.lang.*、java.util.、java.io. 等等,因此随着虚拟机一起加载
  • 运行时加载
    虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的全限定名来加载这个类

那么,加载阶段做了什么,其实加载阶段做了有三件事情:

  • 获取.class文件的二进制流
  • 将类信息、静态变量、字节码、常量这些.class文件中的内容放入方法区中
  • 在内存中生成一个代表这个.class文件的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。一般这个Class是在堆里的,不过HotSpot虚拟机比较特殊,这个Class对象是放在方法区中的

类加载器分类

jvm提供了3个系统加载器,分别是Bootstrp loaderExtClassLoaderAppClassLoader

在这里插入图片描述

这三个加载器互相成父子继承关系

  • Bootstrp loader: Bootstrp加载器是用C++语言写的,它在Java虚拟机启动后初始化;它主要负责加载以下路径的文件:
    %JAVA_HOME%/jre/lib/*.jar
    %JAVA_HOME%/jre/classes/*
    -Xbootclasspath参数指定的路径
    可通过:System.out.println(System.getProperty("sun.boot.class.path"));打印查看

  • ExtClassLoaderExtClassLoader是用Java写的,具体来说就是sun.misc.Launcher$ExtClassLoader,其主要加载:
    %JAVA_HOME%/jre/lib/ext/
    ext下的所有classes目录
    java.ext.dirs系统变量指定的路径中类库
    可通过:System.getProperty("java.ext.dirs")打印查看

  • AppClassLoader: AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的就是它。
    负责加载 -classpath 所指定的位置的类或者是jar文档,也是Java程序默认的类加载器
    System.getProperty("java.class.path")

双亲委派模型

什么是双亲委派

双亲委派模型工作过程是:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即 ClassNotFoundException ),子加载器才会尝试自己去加载。

为什么需要双亲委派模型?

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父加载器已经加载了该类时,就没有必要子加载器再加载一次。
其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为 java.lang.Integer 的类,通过双亲委派模型传递到启动类加载器,而启动类加载器发现这个名字的类,发现该类已被加载,就不会重新加载网络传递过来的 java.lang.Integer ,而直接返回已加载过的Integer.class ,这样便可以防止核心API库被随意篡改。

双亲委派能否打破?
可以的,比如在tomcat中,tomcat通过 war 包进行应用的发布,它其实是违反了双亲委派机制原则,
看一下tomcat类加载的层次结构如下:
在这里插入图片描述

比如:Tomcat的 webappClassLoader 加载web应用下的class文件,不会传递给父类加载器,问题:tomcat的类加载器为什么要打破该模型?
首先一个tomcat启动后是会起一个jvm进程的,它支持多个web应用部署到同一个tomcat里,为此

  • 对于不同的web应用中的class和外部jar包,需要相互隔离,不能因为不同的web应用引用了相同的jar或者有相同的class导致一个加载成功了另一个加载不了。
  • web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,每一个jsp文件对应一个JspClassLoader,它的加载范围仅仅是这个jsp文件所编译出来的那一个.class文件,当Web容器检测到jsp文件被修改时,会替换掉目前JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的热部署功能。

如何实现双亲委派模型

双亲委派模型的原理很简单,实现也简单。每次通过先委托父类加载器加载,当父类加载器无法加载时,再自己加载。其实 ClassLoader 类默认的 loadClass 方法已经帮我们写好了,我们无需去写

几个重要的函数

loadClass 默认实现如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}

再看看 loadClass(String name, boolean resolve) 函数:

protected 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) {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) {// 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(String, boolean) 函数即实现了双亲委派模型!整个大致过程如下:

  • 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  • 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用 parent.loadClass(name, false); ).或者是调用 bootstrap 类加载器来加载
  • 如果父加载器及 bootstrap 类加载器都没有找到指定的类,那么调用当前类加载器的 findClass 方法来完成类加载。

也就是说,如果要自定义类加载器,就要重写fiindClass方法。

抽象类 ClassLoaderfindClass 函数默认是抛出异常的。而前面我们知道, loadClass 在父加载器无法加载类的时候,就会调用我们自定义的类加载器中的 findeClass 函数,因此我们必须要在 loadClass 这个函数里面实现将一个指定类名称转换为 Class 对象

自定义加载器

除了上面的系统提供的3种loader,jvm允许自己定义类加载器,典型的在tomcat上:
在这里插入图片描述

为什么要自定义类加载器:

  • 隔离加载类
    模块隔离,把类加载到不同的应用选中。比如tomcat这类web应用服务器,内部自定义了好几中类加载器,用于隔离web应用服务器上的不同应用程序。
  • 修改类加载方式
    除了Bootstrap加载器外,其他的加载并非一定要引入。根据实际情况在某个时间点按需进行动态加载。
  • 扩展加载源
    比如还可以从数据库、网络、或其他终端上加载
  • 防止源码泄露
    java代码容易被编译和篡改,可以进行编译加密,类加载需要自定义还原加密字节码

自定义类加载器的加载流程

在这里插入图片描述
自定义加载器:

public class MyClassLoader extends ClassLoader {private String codePath;protected MyClassLoader(ClassLoader parent, String path) {super(parent);this.codePath = path;}public MyClassLoader(String classPath) {this.codePath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String fileName = codePath + name + ".class";try (   // 输入流BufferedInputStream bis = new BufferedInputStream(new FileInputStream(fileName));// 输出流ByteArrayOutputStream baos = new ByteArrayOutputStream()) {int len;byte[] data = new byte[1024];while ((len = bis.read(data)) != -1) {baos.write(data, 0, len);}//5.获取内存中字节数组byte[] byteCode = baos.toByteArray();//6.调用defineClass 将字节数组转成Class对象Class<?> defineClass = defineClass(null, byteCode, 0, byteCode.length);return defineClass;} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
}

有以下注意点:

  • 所有用户自定义类加载器都应该继承ClassLoader类

  • 在自定义ClassLoader的子类是,我们通常有两种做法:

    • 重写loadClass方法(是实现双亲委派逻辑的地方,修改他会破坏双亲委派机制,不推荐)
    • 重写findClass方法 (推荐)

验证

连接阶段的第一步,这一阶段的目的是为了确保.class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
Java语言本身是相对安全的语言(相对C/C++来说),但是前面说过,.class文件未必要从Java源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编写来产生.class文件。在字节码语言层面上,Java代码至少从语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工作。

验证阶段主要做以下几方面的工作:

  • 文件格式验证:是不是CAFEBABYE开头,主次版本号是否在当前jvm虚拟机可运行的范围内等
  • 元数据验证:段主要验证属性、字段、类关系、方法等是否合规
  • 字节码验证:这里主要验证class里定义的方法,看方法内部的code是否合法
  • 符号引用验证:字节码里有的是直接引用,有的是指向了其他的字节码地址。而符号引用验证的就是,这些引用的对应的内容是否合法

准备

准备阶段是正式为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配。关于这点,有两个地方注意一下

  • 这时候进行内存分配的仅仅是类变量(被static修饰的变量),而不是实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中
  • 这个阶段赋初始值的变量指的是那些不被final修饰的static变量,比如public static int value = 123value在准备阶段过后是0而不是123,给value赋值为123的动作将在初始化阶段才进行;比如public static final int value =123;就不一样了,在准备阶段,虚拟机就会给value赋值为123。

各个数据类型的零值如下表:
在这里插入图片描述

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。来了解一下符号引用和直接引用有什么区别:

符号引用
符号引用是一种定义,可以是任何字面上的含义,而直接引用就是直接指向目标的指针、相对偏移量。
这个其实是属于编译原理方面的概念,符号引用包括了下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

看一段代码:

public class TestMain {private static int i;private double d;public static void print() {}private boolean trueOrFalse() {return false;}
}

反编译后得到:

Constant pool:#1 = Methodref          #3.#17         // java/lang/Object."<init>":()V#2 = Class              #18            // com/ocean/classloading/TestMain#3 = Class              #19            // java/lang/Object#4 = Utf8               i#5 = Utf8               I#6 = Utf8               d#7 = Utf8               D#8 = Utf8               <init>#9 = Utf8               ()V#10 = Utf8               Code#11 = Utf8               LineNumberTable#12 = Utf8               print#13 = Utf8               trueOrFalse#14 = Utf8               ()Z#15 = Utf8               SourceFile#16 = Utf8               TestMain.java#17 = NameAndType        #8:#9          // "<init>":()V#18 = Utf8               com/ocean/classloading/TestMain#19 = Utf8               java/lang/Object

可以看到常量池中有22项内容,其中带"Utf8"的就是符号引用。比如#2,它的值是"com/ocean/classloading/TestMain",表示的是这个类的全限定名;又比如#4为i,#5为I,它们是一对的,表示变量时Integer(int)类型的,名字叫做i;#12、#16表示的都是方法的名字。

符号引用就是对于类、变量、方法的描述。符号引用和虚拟机的内存布局是没有关系的,引用的目标未必已经加载到内存中了。

直接引用

直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引用在不同

解析阶段负责把整个类激活,串成一个可以找到彼此的网。那这个阶段都做了哪些工作呢?大体可以分为:

  • 类或接口的解析
  • 类方法解析
  • 接口方法解析
  • 字段解析

初始化

最后一个步骤,经过这个步骤后,类信息完全进入了jvm内存,直到它被垃圾回收器回收。
前面几个阶段都是虚拟机来搞定的。我们也干涉不了,从代码上只能遵从它的语法要求。而这个阶段,是赋值,才是我们应用程序中编写的有主导权的地方

初始化阶段就是执行类构造器()方法的过程。 ()方法并不是程序员在Java代码中直接编写 的方法, 它是Javac编译器的自动生成物,()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块) 中的 语句合并产生的。

()方法与类的构造函数(即在虚拟机视角中的实例构造器()方法) 不同, 它不需要显 式地调用父类构造器, Java虚拟机会保证在子类的()方法执行前, 父类的()方法已经执行 完毕。 因此在Java虚拟机中第一个被执行的()方法的类型肯定是java.lang.Object

由于父类的()方法先执行, 也就意味着父类中定义的静态语句块要优先于子类的变量赋值 操作

()方法对于类或接口来说并不是必需的, 如果一个类中没有静态语句块, 也没有对变量的 赋值操作, 那么编译器可以不为这个类生成()方法。 接口中不能使用静态语句块, 但仍然有变量初始化的赋值操作, 因此接口与类一样都会生成 ()方法
但接口与类不同的是, 执行接口的()方法不需要先执行父接口的()方法, 因为只有当父接口中定义的变量被使用时, 父接口才会被初始化。 此外, 接口的实现类在初始化时也 一样不会执行接口的()方法

Java虚拟机必须保证一个类的()方法在多线程环境中被正确地加锁同步, 如果多个线程同 时去初始化一个类, 那么只会有其中一个线程去执行这个类的()方法, 其他线程都需要阻塞等 待, 直到活动线程执行完毕()方法。 如果在一个类的()方法中有耗时很长的操作, 那就 可能造成多个进程阻塞, 在实际应用中这种阻塞往往是很隐蔽的

class TestDeadLoop {static class DeadLoopClass {static {
// 如果不加上这个if语句, 编译器将提示“Initializer does not complete normally”并拒绝编译if (true) {System.out.println(Thread.currentThread() + "init DeadLoopClass");while (true) {}}}}public static void main(String[] args) {Runnable script = new Runnable() {public void run() {System.out.println(Thread.currentThread() + "start");DeadLoopClass dlc = new DeadLoopClass();System.out.println(Thread.currentThread() + " run over");}};Thread thread1 = new Thread(script);Thread thread2 = new Thread(script);thread1.start();thread2.start();}
}

上面说的()方法可以理解为是<cinit>,对象的初始化方法(构造函数),也就是反编译之后看到方法是<init>,这两者什么区别呢?
看一段代码

public class ParentA {static {System.out.println("1");}public ParentA() {System.out.println("2");}
}public class SonB extends ParentA {static {System.out.println("a");}public SonB() {System.out.println("b");}public static void main(String[] args) {ParentA ab = new SonB();ab = new SonB();}}

上面的打印结果是:

1
a
2
b
2
b

其中 static 字段和 static 代码块,是属于类的,在类的加载的初始化阶段就已经被执行。类信息会被存放在方法区,在同一个类加载器下,这些信息有一份就够了,所以上面的 static 代码块只会执行一次,它对应的是 <cinit>方法。

在这里插入图片描述

所以,上面代码的 static 代码块只会执行一次,对象的构造方法执行两次。再加上继承关系的先后原则,不难分析出正确结果
小结:
方法<cinit>的执行时期: 类初始化阶段(该方法只能被jvm调用, 专门承担类变量的初始化工作) ,只执行一次
方法 <init>的执行时期: 对象的初始化阶段

相关文章:

jvm的类加载

文章目录 概要加载类加载器分类双亲委派模型自定义加载器 验证准备解析初始化<cinit>与<init> 概要 jvm运行时的整体结构如下 一个Car类&#xff0c;类跟Car对象的转换过程如下&#xff1a; 加载后的class类信息存放于方法区&#xff1b;ClassLoader只负责clas…...

2024年汉字小达人活动4个多月开赛:18道历年选择题和答案、解析

根据近年的安排&#xff0c;2024年第11届汉字小达人比赛还有4个多月就启动&#xff0c;那么孩子们如何利用这段时间有条不紊地备考呢&#xff1f;我的建议是两手准备&#xff1a;①把小学1-5年级的语文课本上的知识点熟悉&#xff0c;重点是字、词、成语、古诗。②把历年真题刷…...

群晖安装青龙脚本

青龙定时任务管理面板&#xff0c;支持 Python3、JavaScript、Shell、Typescript 这几种环境&#xff0c;通过它可以方便的管理和运行定时任务&#xff08;在某个时间执行一段代码&#xff09;&#xff0c;并且只需简单的配置&#xff0c;就可以在各个平台收到任务执行的结果通…...

【机器学习系列】使用高斯贝叶斯模型进行数据分类的完整流程

目录 一、导入数据 二、选择特征 三、十折交叉验证 四、划分训练集和测试集 五、训练高斯贝叶斯模型 六、预测测试集 七、查看训练集和测试集上的分数 八、查看混合矩阵 九、输出评估指标 一、导入数据 # 根据商户数据预测其是否续约案例 import pandas #读取数据到 da…...

Python中的单例模式:原理、实现与应用

Python中的单例模式&#xff1a;原理、实现与应用 一、引言 在软件开发中&#xff0c;设计模式是一种用于解决常见问题的最佳实践。单例模式&#xff08;Singleton Pattern&#xff09;是这些设计模式中的一种&#xff0c;它确保一个类仅有一个实例&#xff0c;并提供一个全局…...

Linux基础(六):Linux 系统上 C 程序的编译与调试

本篇博客详细分析&#xff0c;Linux平台上C程序的编译过程与调试方法&#xff0c;这也是我们后续程序开发的基础。 目录 一、第一个hello world程序 1.1 创建.c文件 1.2 编译链接 运行可执行程序 二、编译链接过程 2.1 预编译阶段 2.2 编译阶段 2.3 汇编阶段 2.4 链…...

移动硬盘难题:不显示容量与无法访问的解决策略

在使用移动硬盘的过程中&#xff0c;有时会遇到一些棘手的问题&#xff0c;比如移动硬盘不显示容量且无法访问。这种情况让人十分头疼&#xff0c;因为它不仅影响了数据的正常使用&#xff0c;还可能导致重要数据的丢失。接下来&#xff0c;我们就来详细探讨一下这个问题及其解…...

基于springboot+vue的智慧外贸平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

@Async详解,为什么生产环境不推荐直接使用@Async?

一、Async 注解介绍&#xff1a; Async 注解用于声明一个方法是异步的。当在方法上加上这个注解时&#xff0c;Spring 将会在一个新的线程中执行该方法&#xff0c;而不会阻塞原始线程。这对于需要进行一些异步操作的场景非常有用&#xff0c;比如在后台执行一些耗时的任务而不…...

LaTeX 2022软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; LaTeX 2022是基于ΤΕΧ的一种排版系统&#xff0c;特别适用于生成科技和数学文档的高质量打印。它可用于各种文档类型&#xff0c;从简单信函到完整…...

纯干货分享 机器学习7大方面,30个硬核数据集

在刚刚开始学习算法的时候&#xff0c;大家有没有过这种感觉&#xff0c;最最重要的那必须是算法本身&#xff01; 其实在一定程度上忽略了数据的重要性。 而事实上一定是&#xff0c;质量高的数据集可能是最重要的&#xff01; 数据集在机器学习算法项目中具有非常关键的重…...

算法训练营day46

一、单词拆分 元素无重可复选 base case is.length return true&#xff0c;遍历到了最后, 因为ilen s.length&#xff0c;len初始值为1&#xff0c;那么i1 s.length&#xff0c;那么i s.lenth -1 也就是最后一个字符位置 dp(s,i)函数定义&#xff1a;返回 s[i…] 是否能够…...

推荐五个线上兼职,在家也能轻松日入百元,适合上班族和全职宝妈

在这个瞬息万变的时代&#xff0c;你是否也曾考虑过在繁忙的工作之外&#xff0c;寻找一份兼职副业来补贴家用&#xff0c;同时保持生活的多样性&#xff1f;别急&#xff0c;现在就让我为你揭秘五个可靠的日结线上兼职岗位&#xff0c;助你轻松迈向财务自由之路&#xff01; 一…...

Python_文件操作_学习

目录 一、关于文件的打开和关闭 1. 文件的打开 2.文件的关闭 二、文件的读取 1. 文件的读_r 2. 使用readline 3.使用readlines 三、文件的写入 1. 文本的新建写入 2.文本的追加写入 四、文件的删除和重命名 1.文件的重命名 2.文件的删除 五、文件的定位读写 1.t…...

Leetcode 3154. Find Number of Ways to Reach the K-th Stair

Leetcode 3154. Find Number of Ways to Reach the K-th Stair 1. 解题思路2. 代码实现 题目链接&#xff1a;3154. Find Number of Ways to Reach the K-th Stair 1. 解题思路 这一题思路上就是一个动态规划&#xff0c;我们只需要确定一下运行的终止条件&#xff0c;然后写…...

Vue3/Vite引入EasyPlayer.js播放H265视频错误的问题

一、引入EasyPlayer.js github链接:GitHub - EasyDarwin/EasyPlayer.js: EasyPlayer.js H5播放器 将demo/html目录下的 EasyPlayer-element.min.js、EasyPlayer-lib.min.js、EasyPlayer.wasm、jquery.min.js 复制到vue3工程的public目录下,注意,vue3 vite的index.html文件…...

CentOS 7安装alertmanager

说明&#xff1a;本文介绍如何在CentOS 7安装alertmanager&#xff1b; Step1&#xff1a;下载安装包 访问Github仓库&#xff0c;下载对应版本的alertmanager安装包 https://github.com/prometheus/alertmanager/releases 如何查看自己系统的信息&#xff0c;可参考下图中的…...

YOLOv10详细解读 | 一文带你深入了解yolov10的创新点(附网络结构图 + 举例说明)

前言 Hello大家好&#xff0c;我是Snu77&#xff0c;继YOLOv9发布时间没有多久&#xff0c;YOLOv10就紧接着发布于2024.5.23号&#xff08;不得不感叹YOLO系列的发展速度&#xff0c;但要纠正大家的观点就是不是最新的就一定最好&#xff09;&#xff01; 本文给大家带来的是…...

【openlayers系统学习】3.5colormap详解(颜色映射)

五、colormap详解&#xff08;颜色映射&#xff09; ​colormap​ 包是一个很好的实用程序库&#xff0c;用于创建颜色图。该库已作为项目的依赖项添加&#xff08;1.7美化&#xff08;设置style&#xff09;&#xff09;。要导入它&#xff0c;请编辑 main.js​ 以包含以下行…...

Redis教程(十五):Redis的哨兵模式搭建

一、搭建Redis一主二从 分别复制三份Redis工作文件夹&#xff0c;里面内容一致 接着修改7002的配置文件&#xff0c;【redis.windows-service.conf】 port 7002 改成 port 7002 slaveof 127.0.0.1 7001 7003也同样修改 port 7003 slaveof 127.0.0.1 7001 这样就指定了700…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

Appium+python自动化(十六)- ADB命令

简介 Android 调试桥(adb)是多种用途的工具&#xff0c;该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具&#xff0c;其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利&#xff0c;如安装和调试…...

MongoDB学习和应用(高效的非关系型数据库)

一丶 MongoDB简介 对于社交类软件的功能&#xff0c;我们需要对它的功能特点进行分析&#xff1a; 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具&#xff1a; mysql&#xff1a;关系型数据库&am…...

【Linux】C语言执行shell指令

在C语言中执行Shell指令 在C语言中&#xff0c;有几种方法可以执行Shell指令&#xff1a; 1. 使用system()函数 这是最简单的方法&#xff0c;包含在stdlib.h头文件中&#xff1a; #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

(转)什么是DockerCompose?它有什么作用?

一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用&#xff0c;而无需手动一个个创建和运行容器。 Compose文件是一个文本文件&#xff0c;通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

【C++进阶篇】智能指针

C内存管理终极指南&#xff1a;智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...