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

JVM 面试八股文

目录

1. 前言

2. JVM 简介

3. JVM 内存划分

3.1 为什么要进行内存划分

3.2 内存划分的核心区域

3.2.1 核心区域一: 程序计数器

3.2.2 核心区域二: 元数据区

3.2.3 核心区域三: 栈

3.2.4 核心区域四: 堆

4. JVM 类加载机制

4.1 类加载的步骤 

4.1.1 步骤一: 加载

4.1.2 步骤二: 验证

4.1.3 步骤三: 准备

4.1.4 步骤四: 解析

4.1.5 步骤五: 初始化

4.2 类加载触发的时机

4.3 双亲委派模型

4.3.1 三个类加载器

4.3.2 双亲委派模型的工作过程 [经典面试题]

5. 垃圾回收机制(GC)

5.1 什么是 GC

5.1.1 引入 GC 的代价 [拓展]

5.1.2 GC 回收的区域

5.2 GC 工作过程

5.2.1 找到垃圾

5.2.1.1 引用计数 [不太会考]

5.2.1.2 可达性分析

5.2.2 释放垃圾

5.2.2.1 标记-清除

5.2.2.2 复制算法

5.2.2.3 标记-整理

5.2.2.4  分代回收 ★★★


1. 前言

本篇博客的内容完全是面向面试的, 纯八股文内容.

因此, 死记硬背也要记住!! 

2. JVM 简介

JVM(Java Virtual Machine), 即 Java 虚拟机.

虚拟机是指通过软件模拟的具有完整硬件功能的、运行在⼀个完全隔离的环境中的完整计算机系统.

因此,  JVM 就是一台虚拟的, 使用 C/C++ 代码模拟出来的, 现实中不存在的计算机系统.

我们这里仅讨论 JVM 的以下三个关键工作机制(面试中最常考的):

  1. JVM 内存划分
  2. 类加载机制
  3. 垃圾回收机制

接下来, 逐个为大家说明其中的要点内容.

3. JVM 内存划分

3.1 为什么要进行内存划分

JVM 为啥要划分区域呢??

因为 JVM 本就是仿照真实的操作系统来进行设计的, 而真实的操作系统就对进程的地址空间进行了分区.

于是 JVM 也就仿照操作系统的分区的思想, 也进行了内存划分的设计, 对内存进行不同的功能分配.

JVM 从操作系统申请一些内存空间, 其中一些空间供操作系统自身维护使用, 剩下的空间就是 JVM 进程自身来使用的.

JVM 再把这些自身使用的空间进行内存区域的划分(对这些空间进行功能的分配), 就称为 JVM 内存空间划分.

3.2 内存划分的核心区域

 JVM 内存划分的核心区域有四个:

  1. 程序计数器
  2. 元数据区 / 方法区

其中, 元数据区和堆, 整个 Java 进程共用一份:

  1. 各线程类加载好的类对象,都放在同一个元数据区中.
  2. 各线程 new 出的对象, 都放在同一个 堆 中.

而, 程序计数器 和 栈, 一个进程中会存在多份:

  • 每个线程都有各自的 程序计数器 和 栈.

3.2.1 核心区域一: 程序计数器

虽然名字上带有一个 "器" 字, 但是它也是 JVM 划分的一块内存区域.

程序计数器 是一块空间很小的内层区域, 记录的是下一个要执行的指令的地址.

这里 JVM 中的程序计数器和机组(计算机组成原理)中的程序计数器(PC 寄存器)功能很像, 但不是一个东西:

  • JVM 中的程序计数器是位于内存中(JVM 内存划分的一部分)
  • 机组中的程序计数器(PC)位于 CPU 中

3.2.2 核心区域二: 元数据区

元数据区, 在 Java 8 之前叫做方法区.

元数据区保存的是类加载完毕后的数据, 即 类对象.(类对象中有类元信息, 方法元信息)

类对象中包含了以下内容:

  1. 类的名称, 权限修饰限定符(public, private, ...), 继承了哪些类, 实现了哪些接口, ..... 
  2. 方法的名称, 参数的名称, 参数的类型, 返回值的类型, .....

类对象是反射的核心依据. 

元数据区除保存类对象外, 还会保存 static 修饰的成员信息.

.java ==> .class ==> 加载到内存中

想运行 java 代码, 就必须进行类加载, 即: 将 .class 文件加载到内存中, 使得 .class => 类对象

3.2.3 核心区域三: 栈

栈中保存的是方法的调用产生的函数栈帧.

注意: JVM 中的 栈 并非数据结构中的 栈. 

这里 JVM 中的 栈, 是数据结构中的 栈 的应用.

我们写的 Java 代码中肯定存在方法的调用, 而每调用一个方法, 就会在 JVM 的栈中产生一个该方法的栈帧, 当方法调用完毕后, 该方法的栈帧就会被销毁, 返回到调用位置, 代码就能继续往后执行. 

栈帧中包含了这个方法的方法签名, 返回类型, 局部变量, 以及方法结束后代码应该回到哪里继续往后执行等信息.
(
方法签名包括方法的名称和参数列表(参数的类型、顺序和数量))

调用一个方法, 栈中就产生一个栈帧; 方法调用结束, 栈中销毁一个栈帧.

注意: JVM 中的栈, 并非操作系统中的 栈. 这两个功能是相同的, 东西不是一个东西.

  • 操作系统中原生的栈, 保存的是 C/C++ 代码中的函数调用产生的栈帧.
  • JVM 中的栈, 是使用 C/C++ 代码构造出来的(虚拟的, 不是真实存在的), 保存的是 Java 代码中的方法调用产生的栈帧.

JVM 中栈的空间并不大, 大约几十 MB 的大小. 大部分情况下, 由于栈帧会快速的销毁, 这个空间是够用的. 但是在少数情况下, 会出现栈溢出(StackOverFlow), 比如: 死递归.

需要明确一点, JVM 本身就是由 C/C++ 代码实现的, 因此 JVM 中的栈也是通过 C/C++ 代码构造出来的.(也就是说, JVM 解释执行 .class 字节码, 本质是 C/C++ 代码执行的)

而 C/C++ 代码构造 JVM 时, 肯定存在 C/C++ 的函数调用, 也就在操作系统栈中存在函数栈帧. 因此, C/C++ 代码在操作系统栈产生的函数栈帧中, 又构造出了一个 JVM 的栈.

这个 JVM 的栈就是用来放 Java 代码的栈帧的.

但是, 由于 Java 代码中有时也会调用一些 C++ 的代码(native 本地方法, 如: Thread.sleep), 因此存在 操作系统原生的栈 和 JVM 的栈的联合使用.

3.2.4 核心区域四: 堆

JVM 的堆区域中保存的是以下信息:

  1. 类 new 出的对象
  2. 集合类中添加的元素

若有 Test test = new Test();

那么毫无疑问, 其中的 new Test() 一定就是保存在 堆 上的.

但是, 对于引用 test 所在的位置, 需要进行讨论:

  1. 若 test 是一个局部变量(方法中), 那么 test 在栈上.
  2. 若 test 是一个普通成员变量, 那么 test 在堆上.
  3. 若 test 是一个静态成员变量, 那么 test 在元数据区(方法区).

 堆, 是 JVM 中内存最大的区域, 当堆上的元素不再使用的话, 需要进行释放(GC 垃圾回收机制).


4. JVM 类加载机制

JVM 类加载, 就是将字节码文件(.class 文件)加载到内存中, 并将 .calss 文件转换为 类对象(java.lang.Class) 的过程, 以便JVM可以使用这个类.

4.1 类加载的步骤 

类加载在 Java 官方文档上一共有三个阶段. 我们这里将第二个阶段分成三个步骤, 共分五个步骤来讨论:

  1. 加载
  2. 验证
  3. 准备
  4. 解析
  5. 初始化

4.1.1 步骤一: 加载

类加载需要将 .class 文件加载到内存中, 并将 .calss 文件转换为 类对象.

那么第一步就是要找到 .class 文件.

JVM 会根据 类 的全限定名(包名 + 类名, 如 java.lang.String) 来找到对应的 .class 文件, 将文件读取加载到内存中.

上述寻找 .class 文件并加载到内存的过程, 就是 "加载".

这里仅仅是将 .class 文件的内容读到内存中, 还没有对内容进行解析.

4.1.2 步骤二: 验证

验证, 即校验 .class 文件中的内容, 是否是合法的(是否符合官方要求的).

.class 文件的格式, 是 Oracle 官方是有明确要求的, 要求所有的 .class 都必须符合这个格式.

如果读取的 .class 文件符合这个格式, 那就继续往下执行; 如果不符合这个格式, 就会抛异常.

Oracle 官方要求的 .class 文件格式如下:

4.1.3 步骤三: 准备

类对象 是 类加载 最终要产出的内容.(类加载: .class => 类对象)

那么就需要给类对象申请内存空间, 有了空间后, 才能往里面填充内容.

注意: 此时申请的空间中的内容还未进行初始化, 是全 "0" 的空间.

4.1.4 步骤四: 解析

该步骤就是针对字符串常量进行初始化.

即: 将 .class 文件中的字符串常量解析出来(.class 文件中有包含常量池信息的属性), 放到元数据区中的常量池中. 

4.1.5 步骤五: 初始化

该步骤是针对类对象进行最终的初始化.

对步骤三申请好的类对象的内存空间进行初始化, 对类对象的各种属性进行填充, 包括 static 修饰的静态成员.

如果该类具有父类, 且父类未进行类加载, 此环节也会触发父类的类加载.

4.2 类加载触发的时机

需要明确的是, 并不是程序一启动, 就会加载程序中所有存在的类. 

Java 采取的是 懒汉模式/懒加载 去加载类的, 当类被以下情况使用时, 才会触发类加载:

  1. 构造了这个类的实例
  2. 调用了这个类的静态属性/静态方法
  3. 使用这个类的时候, 如果他的父类还没有加载, 也会触发父类的加载.

并且, 一个进程中, 一个类的加载, 只会触发一次.

4.3 双亲委派模型

4.3.1 三个类加载器

双亲委派模型, 更确切的说是 "单亲委派模型".  

这里的 "父子关系" 不是 "父类子类" 的关系, 而是通过 parent 这样引用构成的 "父子关系"(类似二叉树中的父子关系).

双亲委派模型, 就是根据全限定类名(类似 java.lang.String), 寻找 .class 文件. 

JVM 中专门负责类加载的模块, 称为类加载器(可以认为是 JVM 中的一部分代码).

JVM 中默认有三种类加载器:

  1. BootstrapClassLoader (爷)
  2. ExtensionClassLoader  (父)
  3. ApplicationClassLoader  (子)

三个类加载器之间, 构成的就是 双亲委派模型. 并且三个类之间具有的 "父子" 关系如下:

自然, 就是这三个类加载器, 来进行 .class 文件的寻找环节.

4.3.2 双亲委派模型的工作过程 [经典面试题]

上文说到, 三个类加载器, 首当其冲的任务就是寻找 .class 文件.

而三个类加载器, 负责寻找的目录的范围是不同的.

  1. BootstrapClassLoader (爷) ==> 寻找 Java标准库的目录
  2. ExtensionClassLoader  (父) ==> 寻找 Java 拓展库的目录(目前很少用)
  3. ApplicationClassLoader  (子) ==> 寻找 Java 第三方库/当前项目 的目录

其中, Java 拓展库目前已经很少用到了.

而 Java 第三方库, 我们可以理解为: 只要是通过 Maven 下载过来的, 就是第三方库.(例如: jdbc, Spring boot)

双亲委派模型的工作过程如下(类加载时, 根据全限定名, 找 .class 的过程):

  1. 从 ApplicationClassLoader 作为入口, 但不会立即寻找, 而是把类加载的任务交给它的父类 ExtensionClassLoader 来完成
  2. ExtensionClassLoader 也不会立即寻找, 而是也委托给它的父类 BootstrapClassLoader 来进行
  3. BootstrapClassLoader 也想委托给父亲, 但由于它没有父亲, 所以只能自己进行类加载.
  4. 于是, BootstrapClassLoader 根据全限定名, 在 Java 标准库中寻找是否存在匹配的 .class 文件. 找到就加载, 如果没找到, 就再把任务还给孩子 ExtensionClassLoader 来寻找.
  5. 接下来, ExtensionClassLoader 在 Java 拓展库 中寻找 .class 文件, 找到就加载; 没找到就把任务还给孩子 ApplicationClassLoader.
  6. 接下来, ApplicationClassLoader 在 第三方库 中寻找 .class 文件, 找到就加载; 没找到就抛出异常.

设置以上流程的目的是 为了约定 "优先级":

  1. 收到一个类后, 一定是先在标准库中找
  2. 再从扩展库中找
  3. 最后在第三方库找


5. 垃圾回收机制(GC)

5.1 什么是 GC

垃圾回收(GC) 是 Java 释放内存的方式.

并且 Java 之后的各个编程语言, 都引入了 GC.(例如: Python, PHP, js, ....)

为啥这么多语言都选择 GC 机制呢?? 

我们知道, 在 C 语言中需要使用 malloc 来申请内存空间. 并且申请后, 一定要手动调用 free 进行释放, 否则就会出现 内存泄漏.

C 语言的方式, 一方面, 可能会忘记调用 free; 另一方面, 即使写了 free, 但是在一些特殊场景下, 可能无法调用到(例如方法提前 return).

而 GC 可以自动进行内存的释放: JVM 会自动识别出, 某个后续不再使用的内存, 并自动释放掉该内存空间.

因此, GC 可以解决 手动调用 free 所导致的问题.(GC 可以理解是 自动挡, free 可以理解为 手动挡)

因此, 当前进行内存释放最主流的方案, 就是 GC 垃圾回收机制.

5.1.1 引入 GC 的代价 [拓展]

C++ 也是没有引入 GC 机制的.

为啥 GC 这么好, 而 C++ 却没有引入呢?? 因为, 引入 GC 也是需要代价的:

  1. 对程序运行的效率产生影响. (引入 GC 后, 会消耗一定的硬件资源, 拉低性能)
  2. STW(stop the world) 问题: 触发大量 GC 时, 会使得业务代码的执行暂停下来, 等待 GC 结束后再继续执行.(简而言之, 卡了~~)

我们知道 C++ 有两个核心设计理念:

  1. 和 C 兼容
  2. 极致的性能

而 C++ 为了保持它极致性能的理念, 因此没有采用 GC 机制.

虽然 GC 有拉低性能的代价, 但是 GC 发展了这么多年, 已经改进了之前很多的问题~~

在 Java 17 及以上的版本中, 可以做到让 STW < 1ms 的时间.

因此, Java 的性能其实和 C++ 也没差多少.

举个例子, 假设同一段程序:

  1. 用 C++ 实现需要 1 个单位的时间
  2. 用 Java 大概需要 1.5 - 2 个单位的时间
  3. 用 Go 大概 4 - 5 个单位的时间
  4. 用 Python 大概 100 个单位的时间

之所以 Python 这么慢, 主要的原因 Python 的多线程的 "假" 的.

虽然 Python 提供了线程库, 但是 Python 的线程是 "串行" 执行的(臭名昭著的 CIL(全局解释锁) 问题).

5.1.2 GC 回收的区域

GC 回收的是 JVM中的内存空间.(某个对象不再使用时, GC 会进行回收)

为啥只回收堆的内存, JVM 其他内存区域不用释放吗??

  1. 程序技术器: 线程销毁, 随之释放
  2. 元数据区: 存放类加载生成的类对象, 一般不释放
  3. 栈: 方法调用结束, 栈帧随之销毁
  4. 堆: 有新对象创建, 也有旧对象消亡 ==> GC 回收旧对象的内存空间

因此, GC 说是 "回收内存" , 其实本质上是 "回收对象".  不会出现把一个对象 "释放一半" 的情况.

5.2 GC 工作过程

GC 主要有以下两个关键工作:

  1. 找到垃圾(找到不再使用的对象)
  2. 释放垃圾(将对应的内存释放掉)

5.2.1 找到垃圾

GC 的第一步, 找到垃圾, 有以下两个策略:

  1. 引用计数 (Python, PHP)
  2. 可达性分析 (Java)
5.2.1.1 引用计数 [不太会考]

Python, PHP 采用的是 引用计数 的方案.

引用计数, 即每个对象在 new 的时候, 都搭配一个小的内存空间, 保存一个整数, 这个整数就代表当前对象, 有几个引用在指向它.

每次进行引用赋值的时候, 都会自动触发引用计数的修改.(如果一个对象有新的引用指向它, 那计数就 +1; 如果旧的引用不再指向它, 那计数就 -1)

但是, 这个策略具有以下缺陷:

  • 内存消耗加多

搭配一个整数会消耗更多的空间, 尤其是当对象很小的情况下, 引用计数消耗的空间的比例就更大.

假设, 一个引用计数是 4 字节, 而对象本身才有 8 字节, 那么使用引入计数, 直接提高了 50% 的空间占用率.

  • 可能出现 "循环引用" 的情况

情况如下图:

此时, 存在指向两个对象的引用(两个对象中的引用类型的成员互相指向对方), 既不能释放内存, 也无法使用.
(这里的情况有点像死锁, 僵住了~)

Python, PHP 中虽然使用的引用计数, 但其内部搭配了一些方案, 解决了引用计数的循环引用的问题.

5.2.1.2 可达性分析

Java 的 GC 采用的就是 "可达性分析" 这个方案.

上文说到, 引用计数会增加空间的开销; 而可达性分析, 则会增加时间的开销(使用时间环空间).

可达性分析是, 先使用类似算法中 BFS/DFS 的方式遍历来确定哪些对象可达(还在使用), 接着将不可达的对象释放掉.

"可达" 代表该对象还有引用指向它, 不用进行回收.

"不可达" 代表该对象没有引用指向它了, 可以进行释放了.

具体过程如下:

步骤一: 以代码中的一些特定的对象, 作为遍历的起点(GCRoots)

可以作为 GCRoots 的对象如下:

  1. 栈上的局部变量(引用类型)
  2. 常量池引用指向的对象
  3. 静态成员(引用类型)

以上的对象, JVM 都是可以获取到的.

步骤二: 尽可能的进行遍历 ==> 判断哪些对象可以访问到(哪些对象可达)

步骤三: 将每次访问到的对象, 标记为 "可达"

JVM 自身是知道一共有多少个对象的, 通过可达性分析, 知道了哪些是 "可达" 的, 那么剩下的那些对象就是 "不可达" 的, 也就是要回收的 "垃圾". 

接着, 就可以对 "不可达" 的垃圾进行释放了.

因此, 可达性分析可以很好的解决 引用计数 内存占用和循环引用的问题.

这里再通过代码来演示一下可达性分析:

如上图, 将二叉树的根节点做为 GCRoot 进行可达性分析, 对能够访问到的对象标记为 "可达".

如果对代码这样的修改: root.right.right = null; 那么, 就 f 就不可达, 在下一轮的 GC 中, f 将会被当做垃圾回收.

如果这样操作: root.right = null; 那么此时 c 就不可达, c 的不可达也导致了 f 的不可达. 因此, 此时 c 和 f 均不可达, 下轮 GC 中, c 和 f, 均会被当做垃圾回收.

可达性分析, 是周期性的, 每隔一定的时间, 就会触发一次 GC.

一次 GC 遍历花费的时间都很长, 再进行周期性的重复, 那么将花费大量的时间, 这也是 C++ 放弃 GC 的原因.


5.2.2 释放垃圾

5.2.2.1 标记-清除

标记清除, 就是把垃圾对象(不可达对象)的内存, 直接进行释放.

这种策略, 导致内存中的空闲空间不是连续的, 存在内存碎片问题.

标记清除方式释放后的空闲空间, 不是连续的.(内存碎片)

要知道, 申请内存空间时, 只能申请连续的空间. 如果存在大量不连续的空间, 当申请的空间稍微大点时, 就会申请失败.

5.2.2.2 复制算法

在复制算法中, 将内存空间分为相同的两份, 使用的时候, 一次只使用其中的一半.

完成可达性分析后, 将不是垃圾的对象(可达的对象) 拷贝到另一半的内存中, 再把这一半的内存整体释放掉. 如下图所示:

复制算法的优点:

  • 可以确保空闲的内存空间是连续的

复制算法的缺点:

  1. 内存空间利用率大幅度降低(只能使用一半)
  2. 当不是垃圾的对象很多时, 就需要进行大量的复制工作, 复制的成本很高.
5.2.2.3 标记-整理

标记整理 是对 标记清除 方式的优化.

清除垃圾对象后, 再对非垃圾对象进行搬运, 使得内存空间连续:

"搬运" 的过程, 顺序表中对元素的 移动.

标记-整理的优点:

  • 解决了空闲内存不连续的问题(解决了 标记-清除 的问题)
  • 解决了内存利用率低的问题(解决了 复制算法 的问题)

标记-整理的缺点:

  • 依旧存在数据复制(搬运本质就是复制搬运)的开销.(复制成本的问题依旧存在)
5.2.2.4  分代回收 ★★★

上文所提到的三个方式均存在一定的缺陷, 于是, Java 将上述的方式(方式2和方式3)结合起来, 采用 "分代回收" 的方式, 来释放垃圾.

分代回收中的 "代", 指的是 "年龄", 也就是经历的 GC 的轮次. 假设一个对象, 经历一次 GC 后, 没有被当做垃圾清理掉, 那这个对象的年龄就 +1.

分代回收, 针对不同年龄的对象, 采取不同的策略.

为啥, 针对不同年龄的对象, 采取不同的策略呢?? 以下是根据经验的得出的结论:

  1. 如果一个对象是小年轻(年龄小), 这个对象绝大概率会快速挂掉.
  2. 如果一个对象是老油条(年龄大), 这个对象绝大概率会继续存在下去.

基于以上的经验规律, 我们就可以针对不同年龄的对象, 采取不同的策略:

  1. 对于年龄小的对象, GC 的频次可以提高.
  2. 对于年龄大的对象, GC 的频次就可以降低.

因此, Java 将内存空间分为以下几个部分:

  1. 新生代(存放年龄小的对象)
  2. 老年代(存放年龄大的对象)

其中, 新生代又分为:

  1. 伊甸区(存新创建的对象)
  2. 两个幸存区(空间大小相同)

其中, 幸存区要比伊甸区小的多(约 8:1).

将新创建出来的对象, 放入伊甸区中.

伊甸区中绝大部分的对象, 都活不过第一轮 GC(经验规律).

将伊甸区中幸存下来的对象, 使用复制算法, 移到 幸存区 中.

由于幸存下来的对象占少数, 因此幸存区比伊甸区小, 因此复制算法的开销是可控的

幸存区中的对象, 会经过多轮的 GC. 这样下来, 会回收走大部分的对象.

接下来, 再把幸存区 GC 活下来的对象, 使用复制算法移动到的另一个幸存区中.(两个幸存区是等大的, 幸存区很小, 开销也是可控的)

对象会在幸存区中经过多次 GC, 如果都存活了下来, 那么将这些存活下来的移入老年代.

进入老年代中, 说明这个对象极有可能持续存在下去, 因此 GC 频率就可以降低了, 因此开销也就减少了.(老年代中使用 整理-标记 的垃圾释放方式)

因此, JVM 分代回收的策略, 综合使用了各个方式, 使得开销最小化:

  1. 新生代中的对象, 大部分都会快速消亡, 使得 复制算法 的复制开销可控.(只需复制幸存下来的对象)
  2. 老年代中的对象, 大部分生命周期都较长, GC 频次低, 使得 标记整理 的复制开销可控.

其实, JVM 分代回收的机制, 和我们找工作的过程是很像的:

  1. 伊甸区: 公司收到大量的简历. 少数同学经过筛选, 进入笔试面试环节.
  2. 幸存区: 笔试面试环节. 面试大多有很多轮, 少则 2-3 轮, 多则 6-7 轮, 极少数同学通过重重的面试考验.
  3. 老年代: 通过重重考验, 拿到 offer, 称为正式员工. 但, 成为正式员工后, 也不是就稳了, 还有绩效考核, 末尾淘汰.(但是周期就长了, 半年/一年 才考一次)

Java EE 初阶 - END

加油!! 坚持就是胜利!!

相关文章:

JVM 面试八股文

目录 1. 前言 2. JVM 简介 3. JVM 内存划分 3.1 为什么要进行内存划分 3.2 内存划分的核心区域 3.2.1 核心区域一: 程序计数器 3.2.2 核心区域二: 元数据区 3.2.3 核心区域三: 栈 3.2.4 核心区域四: 堆 4. JVM 类加载机制 4.1 类加载的步骤 4.1.1 步骤一: 加载 4…...

STM32 FreeRTOS内存管理简介

在使用 FreeRTOS 创建任务、队列、信号量等对象时&#xff0c;通常都有动态创建和静态创建的方式。动态方式提供了更灵活的内存管理&#xff0c;而静态方式则更注重内存的静态分配和控制。 如果是1的&#xff0c;那么标准 C 库 malloc() 和 free() 函数有时可用于此目的&#…...

【云岚到家】-day02-客户管理-认证授权

第二章 客户管理 1.认证模块 1.1 需求分析 1.基础概念 一般情况有用户交互的项目都有认证授权功能&#xff0c;首先我们要搞清楚两个概念&#xff1a;认证和授权 认证: 就是校验用户的身份是否合法&#xff0c;常见的认证方式有账号密码登录、手机验证码登录等 授权:则是该用…...

【达梦数据库】两地三中心环境总结

目录 架构监视器位置异步备库同步频率配置&#xff1a;dmtimer.ini断网测试异地切换过程&回切&#xff1a;允许丢数据模式切换回切 架构 2&#xff08;1主1实时备库&#xff09;1&#xff08;实时备库&#xff09;1(异步备库)&#xff0c;分别为节点1、2、3、4监视器位置 …...

【springboot 集成 mybatis-plus】

springboot 集成 mybatis-plus 前言实战代码生成器自动填充字段 前言 正如MyBatis-Plus官网所说&#xff0c;MyBatis-Plus 是一个 MyBatis 的增强工具&#xff0c;提供了强大的CRUD操作&#xff0c;支持主键自动生成&#xff0c;代码生成器&#xff0c;自动填充字段等等&#…...

深入浅出 Go语言并发安全字典 sync.Map:原理、使用与优化

深入浅出 Go语言并发安全字典 sync.Map:原理、使用与优化 背景介绍 Go语言作为一种高效的并发编程语言,其标准库中提供了丰富的并发工具,如sync.WaitGroup、sync.Mutex等。然而,在实际开发中,我们经常需要在多个goroutine之间共享数据,这就涉及到并发安全的问题。传统的…...

【Go】Go数据类型详解—指针

1. 前言 在我看来&#xff0c;一门编程语言语法的核心就在于数据类型。而各类编程语言的基本数据类型大致相同&#xff1a;int整型、float浮点型、string字符串类型、bool布尔类型&#xff0c;但是在一些进阶数据类型上就有所不同了。本文将会介绍Go语言当中核心的数据类型——…...

道格拉斯-普克算法(DP)轮廓点精简(Python)

1、介绍 道格拉斯-普克算法由David H. Douglas和Thomas K. Peucker于1973年提出&#xff0c;主要用于简化曲线或折线。而实际中&#xff0c;激光点云的边缘点非常粗糙&#xff0c;如果直接将点进行连接&#xff0c;锯齿问题严重。经过DP算法处理后&#xff0c;数据显示会比较光…...

WPF如何跨线程更新界面

WPF如何跨线程更新界面 在WPF中&#xff0c;类似于WinForms&#xff0c;UI控件只能在UI线程&#xff08;即主线程&#xff09;上进行更新。WPF通过Dispatcher机制提供了跨线程更新UI的方式。由于WPF的界面基于Dispatcher线程模型&#xff0c;当你在非UI线程&#xff08;例如后…...

Ubuntu 24.04 LTS 服务器折腾集

目录 Ubuntu 更改软件源Ubuntu 系统语言英文改中文windows 远程链接 Ubuntu 图形界面Windows 通过 openssh 连接 UbuntuUbuntu linux 文件权限Ubuntu 空闲硬盘挂载到 文件管理器的 other locationsUbuntu 开启 SMB 服务&#xff0c;并通过 windows 访问Ubuntu安装Tailscale&am…...

ROS机器人学习和研究的势-道-术-转型和变革的长期主义习惯

知易行难。说说容易做到难。 例如&#xff0c;不受成败评价影响&#xff0c;坚持做一件事情10年以上&#xff0c;专注事情本身。 机器人专业不合格且失败讲师如何让内心保持充盈的正能量&#xff08;节选&#xff09;-CSDN博客 时间积累 注册20年。 创作历程10年。 创作10年…...

Linux 管道操作

Linux 管道操作 在 Linux 中&#xff0c;管道&#xff08;Pipe&#xff09;是一个非常强大且常用的功能&#xff0c;它允许将一个命令的输出直接传递给另一个命令作为输入&#xff0c;从而能够高效地处理和分析数据。管道在多个命令之间建立数据流&#xff0c;减少了文件的读写…...

【Python】深入探讨Python中的单例模式:元类与装饰器实现方式分析与代码示例

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门&#xff01; 解锁Python编程的无限可能&#xff1a;《奇妙的Python》带你漫游代码世界 单例模式&#xff08;Singleton Pattern&#xff09;是一种常见的设计模式&#xff0c;它确保一个类只有一个实例&…...

imbinarize函数用法详解与示例

一、函数概述 众所周知&#xff0c;im2bw函数可以将灰度图像转换为二值图像。但MATLAB中还有一个imbinarize函数可以将灰度图像转换为二值图像。imbinarize函数是MATLAB图像处理工具箱中用于将灰度图像或体数据二值化的工具。它可以通过全局或自适应阈值方法将灰度图像转换为二…...

【NextJS】PostgreSQL 遇上 Prisma ORM

NextJS 数据库 之 遇上Prisma ORM 前言一、环境要求二、概念介绍1、Prisma Schema Language&#xff08;PSL&#xff09; 结构描述语言1.1 概念1.2 组成1.2.1 Data Source 数据源1.2.2 Generators 生成器1.2.3 Data Model Definition 数据模型定义字段(数据)类型和约束关系&…...

ASP.NET Core - 配置系统之配置提供程序

ASP.NET Core - 配置系统之配置提供程序 3. 配置提供程序3.1 文件配置提供程序3.1.1 JSON配置提供程序3.1.2 XML配置提供程序3.1.3 INI配置提供程序 3.2 环境变量配置提供程序3.3 命令行配置提供程序3.4 内存配置提供程序3.5 配置加载顺序 3.6 默认配置来源 3. 配置提供程序 前…...

【LeetCode: 215. 数组中的第K个最大元素 + 快速选择排序】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…...

【Flink系列】10. Flink SQL

10. Flink SQL Table API和SQL是最上层的API&#xff0c;在Flink中这两种API被集成在一起&#xff0c;SQL执行的对象也是Flink中的表&#xff08;Table&#xff09;&#xff0c;所以我们一般会认为它们是一体的。Flink是批流统一的处理框架&#xff0c;无论是批处理&#xff08…...

JavaScript网页设计案例-JavaScript实现数据脱敏的几种解决方式

数据脱敏是指对数据进行处理&#xff0c;使其在不改变原始数据含义的前提下&#xff0c;降低数据泄露的风险&#xff0c;保护用户隐私。 案例&#xff1a;JavaScript实现数据脱敏 1. 掩码脱敏 掩码脱敏是通过替换或隐藏数据中的部分字符来达到脱敏的效果。常见的掩码方式包括…...

第12篇:从入门到精通:掌握python高级函数与装饰器

第12篇&#xff1a;高级函数与装饰器 内容简介 本篇文章将深入探讨Python中的高级函数与装饰器。您将学习什么是高阶函数&#xff0c;掌握常用的高阶函数如map、filter、reduce的使用方法&#xff1b;理解闭包的概念及其应用&#xff1b;深入了解装饰器的定义与使用&#xff…...

【WiFi帧结构】

文章目录 帧结构MAC头部管理帧 帧结构 Wi-Fi的帧分为三部分组成&#xff1a;MAC头部frame bodyFCS&#xff0c;其中MAC是固定格式的&#xff0c;frame body是可变长度。 MAC头部有frame control&#xff0c;duration&#xff0c;address1&#xff0c;address2&#xff0c;addre…...

通过Wrangler CLI在worker中创建数据库和表

官方使用文档&#xff1a;Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后&#xff0c;会在本地和远程创建数据库&#xff1a; npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库&#xff1a; 现在&#xff0c;您的Cloudfla…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云

目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

Xela矩阵三轴触觉传感器的工作原理解析与应用场景

Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知&#xff0c;帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量&#xff0c;能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度&#xff0c;还为机器人、医疗设备和制造业的智…...

JavaScript 标签加载

目录 JavaScript 标签加载script 标签的 async 和 defer 属性&#xff0c;分别代表什么&#xff0c;有什么区别1. 普通 script 标签2. async 属性3. defer 属性4. type"module"5. 各种加载方式的对比6. 使用建议 JavaScript 标签加载 script 标签的 async 和 defer …...

精益数据分析(98/126):电商转化率优化与网站性能的底层逻辑

精益数据分析&#xff08;98/126&#xff09;&#xff1a;电商转化率优化与网站性能的底层逻辑 在电子商务领域&#xff0c;转化率与网站性能是决定商业成败的核心指标。今天&#xff0c;我们将深入解析不同类型电商平台的转化率基准&#xff0c;探讨页面加载速度对用户行为的…...

Easy Excel

Easy Excel 一、依赖引入二、基本使用1. 定义实体类&#xff08;导入/导出共用&#xff09;2. 写 Excel3. 读 Excel 三、常用注解说明&#xff08;完整列表&#xff09;四、进阶&#xff1a;自定义转换器&#xff08;Converter&#xff09; 其它自定义转换器没生效 Easy Excel在…...