JVM 面试八股文

news2025/1/21 3:40:41

目录

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

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

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

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

相关文章

《AI赋能中国制造2025:智能变革,制造未来》

引言&#xff1a;开启智能制造新时代 在全球制造业格局深度调整的当下&#xff0c;科技变革与产业转型的浪潮汹涌澎湃。2015 年&#xff0c;我国重磅推出《中国制造 2025》这一宏伟战略&#xff0c;它如同一座灯塔&#xff0c;为中国制造业驶向高端化、智能化、绿色化的彼岸指明…

Observability:最大化可观察性 AI 助手体验的 5 大提示(prompts)

作者&#xff1a;来自 Elastic Zoia_AUBRY 在过去三年担任客户工程师期间&#xff0c;我遇到了数百名客户&#xff0c;他们最常问的问题之一是&#xff1a;“我的数据在 Elastic 中&#xff1b;我该如何利用它获得最大优势&#xff1f;”。 如果这适用于你&#xff0c;那么本…

Mysql常见问题处理集锦

Mysql常见问题处理集锦 root用户密码忘记&#xff0c;重置的操作(windows上的操作)MySQL报错&#xff1a;ERROR 1118 (42000): Row size too large. 或者 Row size too large (&#xff1e; 8126).场景&#xff1a;报错原因解决办法 详解行大小限制示例&#xff1a;内容来源于网…

【前端】用OSS增强Hexo的搜索功能

文章目录 前言配置 _config.fluid.yml云端实时更新 local-search.xml解决 OSS.Bucket 的跨域问题 前言 原文地址&#xff1a;https://blog.dwj601.cn/FrontEnd/Hexo/hexo-enhance-local-search-with-oss/ 考虑到某著名云服务商提供的云服务器在两年的 99 计划后续费价格高达四…

ROS2 与机器人视觉入门教程(ROS2 OpenCV)

系列文章目录 前言 由于现有的ROS2与计算机视觉&#xff08;特别是机器人视觉&#xff09;教程较少&#xff0c;因此根据以往所学与积累的经验&#xff0c;对ROS2与机器人视觉相关理论与代码进行分析说明。 本文简要介绍了机器人视觉。首先介绍 ROS2 中图像发布者和订阅者的基…

02内存结构篇(D1_自动内存管理)

目录 一、内存管理 1. C/C程序员 2. Java程序员 二、运行时数据区 1. 程序计数器 2. Java虚拟机栈 3. 本地方法栈 4. Java堆 5. 方法区 运行时常量池 三、Hotspot运行时数据区 四、分配JVM内存空间 分配堆的大小 分配方法区的大小 分配线程空间的大小 一、内存管…

C#,入门教程(01)—— Visual Studio 2022 免费安装的详细图文与动画教程

通过本课程的学习&#xff0c;你可以掌握C#编程的重点&#xff0c;享受编程的乐趣。 在本课程之前&#xff0c;你无需具备任何C#的基础知识&#xff0c;只要能操作电脑即可。 不过&#xff0c;希望你的数学不是体育老师教的。好的程序是数理化的实现与模拟。没有较好的数学基础…

BGP边界网关协议(Border Gateway Protocol)路由引入、路由反射器

一、路由引入背景 BGP协议本身不发现路由&#xff0c;因此需要将其他协议路由&#xff08;如IGP路由等&#xff09;引入到BGP路由表中&#xff0c;从而将这些路由在AS之内和AS之间传播。 BGP协议支持通过以下两种方式引入路由&#xff1a; Import方式&#xff1a;按协议类型将…

【Vim Masterclass 笔记21】S09L39:Vim 设置与 vimrc 文件的用法示例(二)

文章目录 S09L39 Vim Settings and the Vimrc File - Part 21 Vim 的配色方案与 color 命令2 map 命令3 示例&#xff1a;用 map 命令快速生成 HTML 代码片段4 Vim 中的 Leader 键5 用 mkvimrc 命令自动生成配置文件 写在前面 本篇为 Vim 自定义配置的第二部分。当中的每个知识…

owasp SQL 注入-03 (原理)

1: 先看一下注入界面: 点submit 后&#xff0c;可以看到有语法报错&#xff0c;说明已经起作用了: 报如下的错误: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near at line 1 2:…

SpringMVC (1)

目录 1. 什么是Spring Web MVC 1.1 MVC的定义 1.2 什么是Spring MVC 1.3 Spring Boot 1.3.1 创建一个Spring Boot项目 1.3.2 Spring Boot和Spring MVC之间的关系 2. 学习Spring MVC 2.1 SpringBoot 启动类 2.2 建立连接 1. 什么是Spring Web MVC 1.1 MVC的定义 MVC 是…

LabVIEW 实现线路板 PCB 可靠性测试

在电子设备制造领域&#xff0c;线路板 PCB&#xff08;Printed Circuit Board&#xff09;的可靠性直接影响产品的整体性能和使用寿命。企业在生产新型智能手机主板时&#xff0c;需要对 PCB 进行严格的可靠性测试&#xff0c;以确保产品在复杂环境下能稳定运行。传统的测试方…

【大数据2025】Hadoop 万字讲解

文章目录 一、大数据通识大数据诞生背景与基本概念大数据技术定义与特征大数据生态架构概述数据存储数据计算与易用性框架分布式协调服务和任务调度组件数仓架构流处理架构 二、HDFSHDFS 原理总结一、系统架构二、存储机制三、数据写入流程四、心跳机制与集群管理 安全模式&…

【蓝桥杯】43687.赢球票

题目描述 某机构举办球票大奖赛。获奖选手有机会赢得若干张球票。 主持人拿出 N 张卡片&#xff08;上面写着 1⋯N 的数字&#xff09;&#xff0c;打乱顺序&#xff0c;排成一个圆圈。 你可以从任意一张卡片开始顺时针数数: 1,2,3 ⋯ ⋯ 如果数到的数字刚好和卡片上的数字…

Amazon MSK 开启 Public 访问 SASL 配置的方法

1. 开启 MSK Public 1.1 配置 MSK 参数 进入 MSK 控制台页面&#xff0c;点击左侧菜单 Cluster configuration。选择已有配置&#xff0c;或者创建新配置。在配置中添加参数 allow.everyone.if.no.acl.foundfalse修改集群配置&#xff0c;选择到新添加的配置。 1.2 开启 Pu…

学习Hibernate的调优方案

Hibernate是一个非常流行的Java ORM&#xff08;对象关系映射&#xff09;框架&#xff0c;它可以帮助开发者更轻松地处理数据库操作。然而&#xff0c;如果不进行适当的性能调优&#xff0c;Hibernate可能会导致应用程序运行缓慢。本文将详细探讨Hibernate的调优方案&#xff…

基于.Net Core+Vue的文件加密系统

1系统架构图 2 用例图 管理员角色的用例&#xff1a; 文件分享大厅&#xff1a;管理员可以访问文件分享大厅&#xff0c;下载文件。个人信息管理&#xff1a;管理员可以更新自己的个人信息&#xff0c;修改密码。用户管理&#xff1a;管理员负责创建、更新或删除用户账户&…

【React】静态组件动态组件

目录 静态组件动态组件创建一个构造函数(类)使用 class 实现组件**使用 function 实现类组件** 静态组件 函数组件是静态组件&#xff1a; 组件第一次渲染完毕后&#xff0c;无法基于内部的某些操作让组件更新「无法实现自更新」&#xff1b;但是&#xff0c;如果调用它的父组…

Linux UDP 编程详解

一、引言 在网络编程领域&#xff0c;UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;作为一种轻量级的传输层协议&#xff0c;具有独特的优势和适用场景。与 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff0…

AI开发,如何管理我们的提示词

如何系统地管理提示词&#xff1f;推荐一个超好用的工具——Prompt Minder&#xff0c;简直是管理AI提示词的神器&#xff01;如果你经常被一堆乱七八糟的提示词搞得晕头转向&#xff0c;还苦于传统软件没有版本管理&#xff0c;那这个工具绝对能救你于水火之中。 官网&#x…