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

JVM解密: 解构类加载与GC垃圾回收机制

文章目录

  • 一. JVM内存划分
  • 二. 类加载机制
    • 1. 类加载过程
    • 2. 双亲委派模型
  • 三. GC垃圾回收机制
    • 1. 找到需要回收的内存
      • 1.1 哪些内存需要回收?
      • 1.2 基于引用计数找垃圾(Java不采取该方案)
      • 1.3 基于可达性分析找垃圾(Java采取方案)
    • 2. 垃圾回收算法
      • 2.1 标记-清除算法
      • 2.2 标记-复制算法
      • 2.3 标记-整理算法
      • 2.4 分代回收

一. JVM内存划分

JVM 其实是一个 Java 进程,该进程会从操作系统中申请一大块内存区域,提供给 Java 代码使用,申请的内存区域会进一步做出划分,给出不同的用途。

其中最核心的是栈,堆,方法区这几个区域:

  1. 堆,用来放置 new 出来的对象,类成员变量。
  2. 栈,维护方法之间的调用关系,放置局部变量。
  3. 方法区(旧)/元数据区(新):放的是类加载之后的类对象(.class文件),静态变量,二进制指令(方法)。

细分下来 JVM 的内存区域包括以下几个:程序计数器,栈,堆,方法区,图中的元数据区可以理解为方法区。
img

🍂程序计数器:内存最小的一块区域,保存了下一条要执行的指令(字节码)的地址,每个线程都有一份

🍂:储存局部变量与方法之间的调用信息,每一个线程都有一份,但要注意“栈是线程私有的”这种说法是不准确的,私有的意思是我的你是用不了的,但实际上,一个线程栈上的内容,是可以被另一个线程使用到的。

img

栈在 JVM 区域划分中分为两种,一种是 Java 虚拟机栈,另外一种是本地方法栈,这两种栈功能非常类似,当方法被调用时,都会同步创建栈帧来存储局部变量表、操作数栈、动态连接、方法出口等信息。

只不过虚拟机栈是为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是给 JVM 内部的本地(Native)方法服务的(JVM 内部通过 C++ 代码实现的方法)。
img

🍂:储存对象以及对象的成员变量,一个 JVM 进程只有一个,多个线程共用一个堆,是内存中空间最大的区域,Java 堆是垃圾回收器管理的内存区域,后文介绍 GC 的时候细说。

🍂方法区: JDK 1.8 开始,叫做元数据区,存储了类对象,常量池,静态成员变量,即时编译器编译后的代码缓存等数据;所谓的“类对象”,就是被static修饰的变量或方法就成了类属性,.java文件会被编译成.class文件,.class会被加载到内存中,也就被 JVM 构造成类对象了,类对象描述了类的信息,如类名,类有哪些成员,每个成员叫什么名字,权限是什么,方法名等;同样一个 JVM 进程只有一个元数据区,多个线程共用一块元数据区内存。

img

要注意 JVM 的线程和操作系统的线程是一对一的关系,每次在 Java 代码中创建的线程,必然会在系统中有一个对应的线程。

二. 类加载机制

1. 类加载过程

类加载就是把.java文件使用javac编译为.class文件,从文件(硬盘)被加载到内存中(元数据区),得到类对象的过程。(程序要想运行,就需要把依赖的“指令和数据”加载到内存中)。

img
这个图片所示的类加载过程来自官方文档,类加载包括三个步骤:LoadingLinkingInitialization
官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html
img

下面就来了解一下这三步是在干什么:

第一步,加载Loading),找到对应的.class文件,打开并读取文件到内存中,同时通过解析文件初步生成一个代表这个类的 java.lang.Class 对象。

第二步,连接Linking),作用是建立多个实体之间的联系,该过程有包含三个小过程:

  • 验证Verification),主要就是验证读取到的内容是不是和规范中规定的格式完全匹配,如果不匹配,那么类加载失败,并且会抛出异常;一个.class文件的格式如下:img通过观察.class文件结构,其实.class文件把.java文件的核心信息都保留了下来,只不过是使用二进制的方式重新进行组织了,.class文件是二进制文件,这里的格式有严格说明的,哪几个字节表示什么,java官方文档都有明确规定。 来自官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1
  • 准备Preparation),给类对象分配内存空间(先在元数据区占个位置),并为类中定义的静态变量分配内存,此时类变量初始值也就都为 0 值了。
  • 解析Resolution),针对字符串常量初始化,将符号引用转为直接引用;字符串常量,得有一块内存空间,存这个字符的实际内容,还得有一个引用来保存这个内存空间的起始地址;在类加载之前,字符串常量是在.class文件中的,此时这个引用记录的并非是字符串常量真正的地址,而是它在文件的偏移量/占位符(符号引用),也就是说,此时常量之间只是知道它们彼此之间的相对位置,不知道自己在内存中的实际地址;在类加载之后,才会真正的把这个字符串常量给填充到特定的内存地址上中,这个引用才能被真正赋值成指定内存地址(直接引用),此时字符串常量之间相对位置还是一样的;这个场景可以想象你看电影时拿着电影票入场入座。

第三步,初始化(Initialization),这里是真正地对类对象进行初始化,特别是静态成员,调用构造方法,进行成员初始化,执行代码块,静态代码块,加载父类…

🎯类加载的时机

类加载并不是 Java 程序(JVM)一运行就把所有类都加载了,而是真正用到哪个类才加载哪个;整体是一个“懒加载”的策略;只有需要用的时候才加载(非必要,不加载),就会触发以下的加载:

  1. 构造类的实例
  2. 调用这个类的静态方法/使用静态属性
  3. 加载子类就会先加载其父类

一旦加载过后后续使用就不必加载了。

2. 双亲委派模型

双亲委派模型是类加载中的一个环节,属于加载阶段,它是描述如何根据类的全限定名找到.class文件的过程。

在 JVM 里面提供了一组专门的对象,用来进行类的加载,即类加载器,当然既然双亲委派模型是类加载中的一部分,所以其所描述找.class文件的过程也是类加载器来负责的。

但是想要找全.class文件可不容易,毕竟.class文件可能在 jdk 目录里面,可能在项目的目录里面,还可能在其他特定的位置,因此 JVM 提供了多个类加载器,每一个类加载器负责在一个片区里面找。

默认的类加载器主要有三个:

  • BootStrapClassLoader,负责加载 Java 标准库里面的类,如 String,Random,Scanner 等。
  • ExtensionClassLoader,负责加载 JVM 扩展库中的类,是规范之外,由实现 JVM 的组织(Sun/Oracle),提供的额外的功能。
  • ApplicationClassLoader,负责加载当前项目目录中自己写的类以及第三方库中的类。

除了默认的几个类加载器,程序员还可以自定义类加载器,来加载其他目录的类,此时也不是非要遵守双亲委派模型,如 Tomcat 就自定义了类加载器,用来专门加载webapps目录中的.class文件就没有遵守。

img

双亲委派模型就描述了类加载过程中的找目录的环节,它的过程如下:

如果一个类加载器收到了类加载的请求,首先需要先给定一个类的全限定类名,如:“java.lang.String”。

根据类的全限定名找的过程中它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。

因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载(去自己的片区搜索)。

举个例子:我们要去找标准库里面的String.class文件,它的过程大致如下:

  • 首先ApplicationClassLoader类收到类加载请求,但是它先询问父类加载器是否加载过,即询问ExtensionClassLoader类是否加载过。
  • 如果ExtensionClassLoader类没有加载过,请求就会向上传递到ExtensionClassLoader类,然后同理,询问它的父加载器BootstrapClassLoader是否加载过。
  • 如果BootstrapClassLoader没有加载过,则加载请求就会到BootstrapClassLoader加载器这里,由于BootstrapClassLoader加载器是最顶层的加载器,它就会去标准库进行搜索,看是否有String类,我们知道String是在标准库中的,因此可以找到,请求的加载任务完成,这个过程也就结束了。

img
再比如,这里要加载我自己写的的Test类,过程如下:

  • 首先ApplicationClassLoader类收到类加载请求,但是它先询问父类加载器是否加载过,即询问ExtensionClassLoader类是否加载过。
  • 如果ExtensionClassLoader类没有加载过,请求就会向上传递到ExtensionClassLoader类,然后同理,询问它的父加载器BootstrapClassLoader是否加载过。
  • 如果BootstrapClassLoader没有加载过,则加载请求就会到BootstrapClassLoader加载器这里,由于BootstrapClassLoader加载器是最顶层的加载器,它就会去标准库进行搜索,看是否有Test类,我们知道Test类不在标准库,所以会回到子加载器里面搜索。
  • 同理,ExtensionClassLoader加载器也没有Test类,会继续向下,到ApplicationClassLoader加载器中寻找,由于ApplicationClassLoader加载器搜索的就是项目目录,因此可以找到Test类,全过程结束。

如果在ApplicationClassLoader还没有找到,就会抛出异常。
img
总的来说,双亲委派模型就是找.class文件的过程,其实也没啥,就是名字挺哄人。

img

之所以有上述的查找顺序,大概是因为 JVM 代码是按照类似于递归的方式来实现的,就导致了从下到上,又从上到下过程,这个顺序,最主要的目的,就是为了保证 Bootstrap 能够先加载,Application 能够后加载,这就可以避免说因为用户创建了一些奇怪的类,引起不必要的 bug。

三. GC垃圾回收机制

在 C/C++ 中内存空间是需要进行手动释放,如果没有手动去释放那么这块内存空间就会持续存在,一直到进程结束,并且堆的内存生命周期比较长,不像栈随着方法执行结束自动销毁释放,堆默认是不能自动释放的,这就可能导致内存泄露的问题,进一步导致后续的内存申请操作失败。

而在 Java 中引入了 GC 垃圾回收机制,垃圾指的是我们不再使用的内存,垃圾回收就是把我们不用的内存自动释放了。

GC的好处:

  • 非常省心,使程序员写代码更简单一些,不容易出错。

GC的坏处:

  • 需要消耗额外的系统资源,也有额外的性能开销。
  • GC 这里还有一个严重的 STW(stop the world)问题,如果有时候,内存中的垃圾已经很多了,这个时候触发一次 GC 就会消耗大量系统资源,其他程序可能就无法正常执行了;GC 可能会涉及一些锁操作,就可能导致业务代码无法正常执行;极端情况下可会卡顿几十毫秒甚至上百毫秒。

GC 的实际工作过程包含两部分:

  1. 找到/判定垃圾。
  2. 再进行垃圾的释放。

1. 找到需要回收的内存

1.1 哪些内存需要回收?

Java 程序运行时,内存分为四个区,分别是程序计数器,栈,堆,方法区。
对于程序计数器,它占据固定大小的内存,它是随着线程一起销毁的,不涉及释放,那么也就用不到 GC;对于栈空间,函数执行完毕,对应的栈帧自动销毁释放了,也不需要 GC;对于方法区,主要进行类加载,虽然需要进行“类卸载”,此时需要释放内存,但是这个操作的频率是非常低的;最后对于堆空间,经常需要释放内存,GC 也是主要针对堆进行释放的。

在堆空间,内存的分布有三种,一是正在使用的内存,二是不用了但未回收的内存,三是未分配的内存,那内存中的对象,也有三种情况,对象内存全部在使用(相当于对象整体全部在使用),对象的内存部分在使用(相当于对象的一部分在使用),对象的内存不使用(对象也就使用完毕了),对于这三类对象,前两类不需要回收,只有最后一类是需要回收的。
img
所以,垃圾回收的基本单位是对象,而不是字节,对于如何找到垃圾,常用有引用计数法与可达性分析法两种方式,关键思路是,抓住这个对象,看看到底有没有“引用”指向它,没有引用了,它就是需要被释放的垃圾。

1.2 基于引用计数找垃圾(Java不采取该方案)

所谓基于引用计数判断垃圾,就是给每一个对象分配一个计数器(整数),来记录该对象被多少个引用变量所指,每次创建一个引用指向该对,,计数器就+1,每次该引用被销毁了计数器就–1,如果这个计数器的值为0则表示该对象需要回收,比如有一个Test对象,它被三个引用所指,所以这个 Test 对象所带计数器的值就是3

//伪代码:
Test t1 = new Test();
Test t2 = t1;
Test t3 = t1;

img

如果上述的伪代码是在一个方法中,待方法执行完毕,方法中的局部引用变量被销毁,那么Test对象的引用计数变为0,此时就会被回收。

由此可见,基于引用计数的方案非常简单高效并且可靠,但是它拥有两个致命缺陷:

  1. 内存空间浪费较多(利用率低), 需要给每个对象分配一个计数器,如果按照4个字节来算;代码中的对象非常少时无所谓,但如果对象特别多了,占用的额外空间就会很多,尤其是每个对象都比较小的情况下。
  2. 存在循环引用的问题,会出现对象既不使用也不释放的情况,看下面举例子来分析一下。

有以下一段伪代码:

class Test {Test t = null;
}//main方法中:
Test t1 = new Test(); // 1号对象, 引用计数是1
Test t2 = new Test(); // 2号对象, 引用计数是1
t1.t = t2;            // t1.t指向2号对象, 此时2号对象引用计数是2
t2.t = t1;            // t1.t指向1号对象, 此时1号对象引用计数是2

执行上述伪代码,运行时内存图如下:

img

然后,我们把变量t1与t2置为null,伪代码如下:

//伪代码:
t1 = null;
t2 = null;

执行完上面伪代码,运行时内存图如下:
img
此时 t1 和 t2 引用销毁了,一号对象和二号对象的引用计数都-1,但由于两个对象的属性相互指向另一个对象,计数器结果都是1而不是0造成对象无法及时得到释放,而实际上这个两个对象已经获取不到了(应该销毁了)。

1.3 基于可达性分析找垃圾(Java采取方案)

Java 中的对象都是通过引用来指向并访问的,一个引用指向一个对象,对象里的成员又指向别的对象。

所谓可达性分析,就是通过额外的线程,将整个 Java 程序中的对象用链式/树形结构把所有对象串起来,从根节点出发去遍历这个树结构,所有能访问到的对象,标记成“可达”,不能访问到的,就是“不可达”,JVM 有一个所有对象的名单(每 new 一个对象,JVM 都会记录下来,JVM 就会知道一共有哪些对象,每个对象的地址是什么),通过上述遍历,将可达的标记出来,剩下的不可达的(未标记的)就可以作为垃圾进行回收了。

可达性分析的起点称为GC Roots(就是一个Java对象),一个代码中有很多这样的起点,把每个起点都遍历一遍就完成了一次扫描。
img

对于这个GCRoots,一般很难被回收,它来源可以分为以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,例如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在本地方法栈中 JNI(即通常所说的Native方法)引用的对象。
  • 常量池中引用所指向的对象。
  • 方法区中静态成员所指向的对象。
  • 所有被同步锁(synchronized 关键字)持有的对象。

可达性分析克服了引用计数的两个缺点,但它有自己的问题:

  1. 需要进行类似于 “树遍历”的过程,消耗更多的时间,但可达性分析操作并不需要一直执行,只需要隔一段时间执行一次寻找不可达对象,确定垃圾就可以,所以,慢一下点也是没关系的,虽迟,但到。
  2. 可达性分析过程,当前代码中的对象的引用关系发生变化了,还比较麻烦,所以为了准确的完成这个过程,就需要让其他的业务暂停工作(STW问题),但 Java 发展这么多年,垃圾回收机制也在不断的更新优化,STW 这个问题,现在已经能够比较好的应对了,虽不能完全消除,但也已经可以让 STW 的时间尽量短了。

2. 垃圾回收算法

垃圾回收的算法最常见的有以下几种:

  1. 标记-清除算法
  2. 标记-复制算法
  3. 标记-整理算法
  4. 分代回收算法(本质就是综合上述算法,在堆的不同区采取不同的策略)

2.1 标记-清除算法

标记其实就是可达性分析的过程,在可达性分析的过程中,会标记可达的对象,其不可达的对象,都会被视为垃圾进行回收。

比如经过一轮标记后,标记状态和回收后状态如图:

img
我们发现,内存是释放了,但是回收后,未分配的内存空间是零散的不是连续的,我们知道申请内存的时候得到的内存得是连续的,虽然内存释放后总的空闲空间很大,但由于未分配的内存是碎片化的,就有可能申请内存失败;假设你的主机有 1GB 空闲内存,但是这些内存是碎片形式存在的,当申请 500MB 内存的时候,也可能会申请失败,毕竟不能保证有一块大于 500MB 的连续内存空间,这也是标记-清除算法的缺陷(内存碎片问题)。

2.2 标记-复制算法

为了解决标记-清除算法所带来的内存碎片化的问题,引入了复制算法。

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,每次清理,就将还存活着的对象复制到另外一块上面,然后再把已使用过的这一块内存空间一次清理掉。

复制算法的第一步还是要通过可达性分析进行标记,得到哪一部分需要进行回收,哪一部分需要保留,不能回收。

标记完成后,会将还在使用的内存连续复制到另外一块等大的内存上,这样得到的未分配内存一直都是连续的,而不是碎片化的。

img
但是,复制算法也有缺陷:

  • 空间利用率低。
  • 如果垃圾少,有效对象多,复制成本就比较大。

2.3 标记-整理算法

标记-整理算法针对复制算法做出进一步改进,其中的标记过程仍然与“标记-清除”算法一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。

img
回收时是将存活对象按照某一顺序(比如从左到右,从上到下的顺序)拷贝到非存活对象的内存区域,类似于顺序表的删除操作,会将后面的元素搬运到前面。
解决了标记-复制算法空间利用率低的问题,也没有内存碎片的问题,但是复制的开销问题并没有得到解决。

2.4 分代回收

上述的回收算法都有一定的缺陷,分代回收就是将上述三种算法结合起来分区使用,分代回收会针对对象进行分类,以熬过的 GC 扫描轮数作为“年龄”,然后针对不同年龄采取不同的方案

分代是基于一个经验规律,如果一个东西存在时间长了,那么接下来大概率也会存在(要没有早就没有了)。

我们知道 GC 主要是回收堆上的无用内存,我们先来了解一下堆的划分,堆包括新生代(Young)、老年代(Old),而新生代包括一个伊甸区(Eden)与两个幸存区(Survivor),分代回收算法就会根据不同的代去采取不同的标记-xx算法。

img
在新生代,包括一个伊甸区与两个幸存区,伊甸区存储的是未经受 GC 扫描的对象(年龄为 0),也就是刚刚 new 出来的对象。

幸存区存储了经过若干轮 GC 扫描的对象,通过实际经验得出,大部分的 Java 对象具有“朝生夕灭”的特点,生命周期非常短,也就是说只有少部分的伊甸区对象才能熬过第一轮的 GC 扫描到幸存区,所以到幸存区的对象相比于伊甸区少的多,正因为大部分新生代的对象熬不过 GC 第一轮扫描,所以伊甸区与幸存区的分配比例并不是1:1的关系,HotSpot 虚拟机默认一个 Eden 和一个 Survivor 的大小比例是 8∶1,正因为新生代的存活率较小,所以新生代使用的垃圾回收算法为标记-复制算法最优,毕竟存活率越小,对于标记-复制算法,复制的开销也就很小。

不妨我们将第一个 Survivor 称为活动空间,第二个 Survivor 称为空闲空间,一旦发生 GC,会将 10% 的活动区间与另外 80% 伊甸区中存活的对象复制到 10% 的空闲空间,接下来,将之前 90% 的内存全部释放,以此类推。

在后续几轮 GC 中,幸存区对象在两个 Survivor 中进行标记-复制算法,此处由于幸存区体积不大,浪费的空间也是可以接受的。

在继续持续若干轮 GC 后(这个对象已经再两个幸存区中来回考贝很多次了),幸存区的对象就会被转移到老年代,老年代中都是年龄较老的对象,根据经验,一个对象越老,继续存活的可能性就越大(要挂早挂了),因此老年代的 GC 扫描频率远低于新生代,所以老年代采用标记-整理的算法进行内存回收,毕竟老年代存活率高,对于标记-整理算法,复制转移的开销很低。

还要注意一个特殊情况,如果对象非常大,就直接进入老年代,因为大对象进行复制算法,成本比较高,而且大对象也不会很多。

相关文章:

JVM解密: 解构类加载与GC垃圾回收机制

文章目录 一. JVM内存划分二. 类加载机制1. 类加载过程2. 双亲委派模型 三. GC垃圾回收机制1. 找到需要回收的内存1.1 哪些内存需要回收?1.2 基于引用计数找垃圾(Java不采取该方案)1.3 基于可达性分析找垃圾(Java采取方案) 2. 垃圾回收算法2.1 标记-清除算法2.2 标记…...

【Spring Boot】Spring Boot结合MyBatis简单实现学生信息管理模块

实战:实现学生信息管理模块 环境准备 JDKSpring BootMyBatis 创建Spring Boot项目 使用Spring Initializr创建一个新的Spring Boot项目,并添加以下依赖: Spring WebMyBatis FrameworkMySQL Driver 数据库设计 在MySQL数据库中创建一个名…...

【Java List与Map】List<T> Map与Map List<T>的区别(126)

List<T> Map:List里面的数据类型包含Map; Map List<T>:Map里面value的数据类型包含List; 测试案例: import java.util.ArrayList; import java.util.HashMap; import j…...

【FreeRTOS】常用函数总结

xTaskCreate(): 用法: xTaskCreate(taskFunction, taskName, stackSize, parameters, priority, taskHandle)参数: taskFunction:任务函数,即任务的入口函数。taskName:任务的名称。stackSize:任…...

The Cherno——OpenGL

The Cherno——OpenGL 1. 欢迎来到OpenGL OpenGL是一种跨平台的图形接口(API),就是一大堆我们能够调用的函数去做一些与图像相关的事情。特殊的是,OpenGL允许我们访问GPU(Graphics Processing Unit 图像处理单元&…...

linux中学习控制进程的要点

1. 进程创建 1.1 fork函数 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;自进程中返回0&#xff0c;父进程返回子进程id&#xff0c;出错返回-1 进程调用fork&#xff0c;当控制转移到内核中的fork代码后&#xff0c;内核会做以下操作 分配新的内存块和…...

C++Qt QSS要注意的坑

qss源自css&#xff0c;相当于css的一个子集&#xff0c;主要支持的是css2标准&#xff0c;很多网上的css3的标准的写法在qss这里是不生效的&#xff0c;所以不要大惊小怪。 qss也不是完全支持所有的css2&#xff0c;比如text-align官方文档就有说明&#xff0c;只支持 QPushB…...

LeetCode每日一题:56. 合并区间(2023.8.27 C++)

目录 56. 合并区间 题目描述&#xff1a; 实现代码与解析&#xff1a; 排序 贪心 原理思路&#xff1a; 56. 合并区间 题目描述&#xff1a; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间&…...

电视盒子什么牌子好?经销商整理线下热销电视盒子品牌排行榜

在面对众多品牌和型号时&#xff0c;不知道电视盒子哪个牌子好的消费者超多&#xff0c;很多人进店都会问我电视盒子哪款好&#xff1f;我根据店铺内近两年的销量情况整理了电视盒子品牌排行榜&#xff0c;看看实体店哪些电视盒子最值得入手吧。 TOP 1.泰捷WEBOX 40Pro Max电视…...

JavaScript关于函数的小挑战

题目 回到两个体操队&#xff0c;即海豚队和考拉队! 有一个新的体操项目&#xff0c;它的工作方式不同。 每队比赛3次&#xff0c;然后计算3次得分的平均值&#xff08;所以每队有一个平均分&#xff09;。 只有当一个团队的平均分至少是另一个团队的两倍时才会获胜。否则&…...

机器学习深度学习——针对序列级和词元级应用微调BERT

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——NLP实战&#xff08;自然语言推断——注意力机制实现&#xff09; &#x1f4da;订阅专栏&#xff1a;机…...

重启Mysql时报错rm: cannot remove ‘/var/lock/subsys/mysql‘: Permission denied

只有用mysql重启时报错&#xff0c;用root不报错 [mysqlt3-dtpoc-dtpoc-web04 bin]$ service mysql restart Shutting down MySQL.. SUCCESS! rm: cannot remove /var/lock/subsys/mysql: Permission denied Starting MySQL.. SUCCESS! [roott3-dtpoc-dtpoc-web04 ~]# serv…...

[C/C++]指针详讲-让你不在害怕指针

个人主页&#xff1a;北海 &#x1f390;CSDN新晋作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;C/C&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;大家一起学习交流&#xff01;&#x1f9…...

无涯教程-Android - Frame Layout函数

Frame Layout 旨在遮挡屏幕上的某个区域以显示单个项目&#xff0c;通常&#xff0c;应使用FrameLayout来保存单个子视图&#xff0c;因为在子视图彼此不重叠的情况下&#xff0c;难以以可扩展到不同屏幕尺寸的方式组织子视图。 不过&#xff0c;您可以使用android:layout_grav…...

docker desktop安装es 并连接elasticsearch-head:5

首先要保证docker安装成功&#xff0c;打开cmd&#xff0c;输入docker -v&#xff0c;出现如下界面说明安装成功了 下面开始安装es 第一步&#xff1a;拉取es镜像 docker pull elasticsearch:7.6.2第二步&#xff1a;运行容器 docker run -d --namees7 --restartalways -p 9…...

计网(第四章)(网络层)(六)

目录 一、路由选择协议&#xff08;动态路由自动获取路由信息&#xff09;概述&#xff1a; 二、因特网采用的路由协议 主要特点&#xff1a; 1.自适应 2.分布式 3.分层次 因特网采用分层次的路由选择协议&#xff1a; 三、常见的路由选择协议 一、路由选择协议&#xff…...

科研无人机平台P600进阶版,突破科研难题!

随着无人机技术日益成熟&#xff0c;无人机的应用领域不断扩大&#xff0c;对无人机研发的需求也在不断增加。然而&#xff0c;许多开发人员面临着无法从零开始构建无人机的时间和精力压力&#xff0c;同时也缺乏适合的软件平台来支持他们的开发工作。为了解决这个问题&#xf…...

Apache的简单介绍(LAMP架构+搭建Discuz论坛)

文章目录 1.Apache概述1.1什么是apache1.2 apache的功能及特性1.2.1功能1.2.2特性 1.3 MPM 工作模式1.3.1 prefork模式1.3.2 worker模式1.3.3 event模式 2.LAMP概述2.1 LAMP的组成2.2 LAMP各组件的主要作用2.3 LAMP的工作过程2.4CGI和FastCGI 3.搭建Discuz论坛所需4.编译安装Ap…...

CDL基础原理

一、CDL简介 CDL&#xff08;全称Change Data Loader&#xff09;是一个基于Kafka Connect框架的实时数据集成服务。 CDL服务能够从各种OLTP数据库中捕获数据库的Data Change事件&#xff0c;并推送到kafka&#xff0c;再由sink connector推送到大数据生态系统中。 CDL目前支…...

WPF基础入门-Class7-WPF-MVVN框架

WPF基础入门 Class7-MVVN框架 使用框架可以省掉如Class6中的ViewModelBase.cs的OnPropertyChanged&#xff0c;亦方便命令传参 1、NuGet安装CommunityToolkit.Mvvm&#xff08;原Mircrosoft.Toolkit.Mvvm&#xff09;也可以安装MVVMLight等其他集成库 2、显示页面&#xff1…...

React第五十七节 Router中RouterProvider使用详解及注意事项

前言 在 React Router v6.4 中&#xff0c;RouterProvider 是一个核心组件&#xff0c;用于提供基于数据路由&#xff08;data routers&#xff09;的新型路由方案。 它替代了传统的 <BrowserRouter>&#xff0c;支持更强大的数据加载和操作功能&#xff08;如 loader 和…...

遍历 Map 类型集合的方法汇总

1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1

每日一言 生活的美好&#xff0c;总是藏在那些你咬牙坚持的日子里。 硬件&#xff1a;OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写&#xff0c;"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...