Java虚拟机:运行时内存结构
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 035 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自己的技术栈的同学。与此同时,本专栏的所有文章,也都会准备充足的代码示例和完善的知识点梳理,因此也十分适合零基础的小白和要准备工作面试的同学学习。当然,我也会在必要的时候进行相关技术深度的技术解读,相信即使是拥有多年 Java 开发经验的从业者和大佬们也会有所收获并找到乐趣。
–
运行时内存结构是 JVM 在执行 Java 程序时管理和分配内存资源的基础。不同的内存区域负责存储不同类型的数据,如堆内存用于对象实例,方法区用于类信息等。本篇文章将详细探讨 JVM 的运行时内存结构,帮助你理解内存管理的原理,以及如何避免常见的内存相关问题。
文章目录
- 1、Java自动分配内存结构
- 1.1、C语言:精确掌控内存的雕刻师
- 1.2、Java:Jvm自动内存舞蹈的舞伴
- 1.3、从C到Java:内存管理的进化
- 2、Java运行时的内存结构
- 2.1、堆(Heap)
- 2.2、方法区(Method Area)/ 元空间(Metaspace)
- 2.3、运行时常量池
- 2.4、虚拟机栈(Java Virtual Machine Stacks)
- 2.5、本地方法栈(Native Method Stack)
- 2.6、程序计数器(Program Counter Register)
- 2.7、直接内存(Direct Memory)
- 3、相关参数设定
- 3.1、堆内存设置
- 3.2、元空间(Metaspace)设置
- 3.3、垃圾回收器设置
- 3.4、直接内存设置
- 3.5、其他性能调优参数
1、Java自动分配内存结构
在编程世界里,内存管理一直是一个令人着迷的话题。在 C 语言中,内存的使用主要涉及到手动分配、使用和释放内存。这与 Java 的自动垃圾回收机制不同。C 语言通过指针直接操作内存,而 Java 隐藏了这些底层细节。
1.1、C语言:精确掌控内存的雕刻师
想象一下,你是一个雕刻家,手中拿着凿子和锤子,面对一块未经雕琢的大理石。在C语言中,内存管理就像这样的雕刻过程。你需要精确地决定从哪里切割、去除多少材料,每一下都要小心翼翼。
看看这个 C 语言的例子:
#include <stdio.h>
#include <stdlib.h>int main() {int *ptr; // 这就像是指向你的大理石块的指针int n = 5; // 你想雕刻的数字数量ptr = (int*)malloc(n * sizeof(int)); // 你在大理石块上划出你要工作的区域if (ptr == NULL) {printf("Memory allocation failed.\n"); // 如果大理石块有问题,你就停止工作exit(1);}for (int i = 0; i < n; i++) {ptr[i] = i + 1; // 在每个区域雕刻一个数字}free(ptr); // 完成后,清理掉所有碎屑return 0;
}
在这个例子中,我们使用 malloc(内存分配)函数在内存中划出一块区域,就像在大理石上划出我们要雕刻的部分。然后我们在这块区域上工作,最后用 free 函数清理掉所有碎屑,释放这块区域。这需要精确的控制和细致的关注,一点小错误就可能导致整个作品毁坏。
1.2、Java:Jvm自动内存舞蹈的舞伴
现在,想象你是一个舞者,参加一场精心编排的舞蹈。在 Java 世界中,Jvm 像是你的舞伴,它自动地引导你,你只需要专注于舞蹈的步伐和节奏。
看看这个 Java 的例子:
public class Main {public static void main(String[] args) {int n = 5;int[] arr = new int[n]; // Jvm 为你准备了舞台for (int i = 0; i < arr.length; i++) {arr[i] = i + 1; // 你在舞台上跳舞,每一步都被自动记录}// 舞蹈结束后,Jvm 会清理舞台,你无需担心}
}
在这个例子中,当我们创建一个数组时,Jvm 自动在内存中为它分配空间。这就像是自动为你准备舞台。你只需要在这个空间上"跳舞"(编写代码)。当舞蹈结束,也就是当我们的数组不再需要时,Jvm 会自动进行"清理工作"(回收内存)。这个过程完全自动化,免去了你手动管理内存的麻烦。
1.3、从C到Java:内存管理的进化
通过这两个例子,我们看到了 C 和 Java 在内存管理上的根本不同。在 C 中,程序员必须像雕刻家一样精确地掌握每一块内存。而在 Java 中,Jvm 的垃圾回收机制就像是一位神奇的舞伴,它自动地引导着内存的分配和释放。这不仅简化了编程过程,也减少了错误的可能性,让程序员可以专注于创造性的工作。
这种从 C 到 Java 的转变,不仅是从手动到自动的变化,更是一种编程哲学的进化。在C中,你是内存的主宰,每一个决定都由你控制。而在 Java 中,你交出了部分控制,以换取更高的安全性和开发效率。Java 的自动内存管理减少了错误的可能性,使程序员可以更自由地探索更复杂的逻辑和架构。
2、Java运行时的内存结构
Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(Java SE 7版)》的规定,Java 虚拟机所管理的内存将会包括以下几个运行时数据区域:

2.1、堆(Heap)
这是 Jvm 中最大的一块内存区域。也是被所有线程共享的一块内存区。用于存储 Java 应用程序创建的对象实例和数组,堆是也在虚拟机启动时创建的,并且是垃圾收集器管理的主要区域,这里的对象不需要手动释放。
Java 堆在存储内容的构成上主要有:
-
对象实例:几乎所有通过
new关键字创建的对象都在堆上分配。这包括用户自定义类的对象以及内置类的对象实例(如Object、Integer、String等)。 -
数组:无论是基本类型数组还是对象数组,都在堆上分配。
-
类实例变量:对象的非静态字段(无论是基本类型还是对象引用)随对象实例一起存储在堆中。
-
字符串常量池:从 Java 7 开始,字符串常量池被存储在堆中。包括所有的字符串字面量和通过
String.intern()方法显式添加到池中的字符串实例。字符串常量池帮助节省内存,因为它存储了唯一的字符串实例,当创建已存在于池中的字符串时,直接返回池中的引用。 -
Jvm 内部结构:包括但不限于,某些 Jvm 内部维护的结构,如方法调用时的参数、返回值等。
-
垃圾收集根节点:活跃的线程、静态字段引用的对象、JNI 引用等,这些作为 GC Roots,垃圾收集器会从这些根节点开始搜索,以确定哪些对象是可达的,哪些是垃圾。
-
其他可能的结构或对象:根据不同 Jvm 的实现细节,可能还会有其他类型的数据结构或对象存储在堆上。
总结来说,Java 堆是存储 Java 应用程序创建的大部分动态数据的地方。这不仅包括各种类型的对象和数组,还包括特殊的结构如字符串常量池。堆的管理和垃圾回收机制对于 Java 应用程序的性能和稳定性至关重要。
此外 Java 堆也是垃圾回收的主要场所。因此很多时候也被称做 GC 堆(Garbage Collected Heap)。
从内存回收的角度来看 Jvm 的内存分区:
- Java 堆通常分为年轻代(Young Generation)和老年代(Old Generation)。
- 年轻代又被进一步分为一个 Eden 空间和两个 Survivor 空间(通常称为 S0 和 S1)。
- 对象最初在年轻代中创建,随着垃圾回收的进行和对象的存活时间增加,它们可能会被移动到老年代。
2.2、方法区(Method Area)/ 元空间(Metaspace)
方法区(Method Area)也常常被称为永久代(Permanent Generation,简称 PermGen),但实际上是两个不同的概念,在 Java 的不同版本中扮演不同的角色。
- 方法区(Method Area):方法区是 Java 虚拟机规范中定义的一个内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是 JVM 规范的一部分,因此所有的 JVM 实现都必须有方法区,但具体实现可以有所不同。
- 永久代(PermGen):永久代是方法区的一种实现,出现在 Sun/Oracle 的 Jvm 实现中,直到 Java 8 为止。它使用固定的内存大小来存储类的元数据,容易导致 OutOfMemoryError 异常,如果这部分内存分配不足。
- 在 Java 8 中,Oracle 抛弃了永久代的概念,引入了一个名为"元空间"(Metaspace)的新内存区域来替代永久代。元空间使用本地内存(而非虚拟机内存),因此不受 Java 堆大小的限制。
总结来说,方法区是一个抽象概念,而永久代是这个概念在某些 Jvm 版本中的具体实现。从 Java 8 开始,永久代被元空间所取代,这是一种更灵活的内存管理方式。
以 JDK 8 之前的 HotSpot 实现中的方法区举例,它的主要内容有:
- 类信息:类的名称、访问修饰符、常量池、字段描述、方法描述等。
- 静态变量:类中定义的静态变量,因为它们不随对象实例存在而存在,而是随类的加载和卸载而存在。
- 常量池:包括各种字面量和符号引用,这部分内容在类加载后进入方法区的常量池。
- 即时编译后的代码:如果使用即时编译器(JIT),编译器编译后的代码也可能存储在此区域。
2.3、运行时常量池
Java运行时常量池(Runtime Constant Pool)是Java虚拟机在运行时存储常量的一块区域。它是方法区的一部分,用于存储编译期生成的各种字面量和符号引用。
Java运行时常量池的主要作用有以下几个方面:
-
存储字面量:Java中的字符串、整数、浮点数等字面量都会被存储在运行时常量池中。这些字面量在编译期就确定了,存储在常量池中可以提高运行效率。
-
存储符号引用:Java中的类、方法、字段等符号引用也会被存储在运行时常量池中。符号引用是一种直接或间接指向目标的引用,存储在常量池中可以方便解析和使用。
-
动态生成的常量:在运行时,Java虚拟机还可以动态生成一些常量,并将其存储在运行时常量池中。例如通过String类的intern()方法将字符串对象加入常量池。
需要注意的是,运行时常量池是每个类的独立部分,它在类加载时被创建并初始化。不同类的常量池是相互独立的,即使两个类的常量内容相同,它们在运行时常量池中的地址也是不同的。
运行时常量池的存在可以提高Java程序的性能和效率,减少了重复的常量创建和存储。同时,它也为Java提供了一些特性,如字符串常量池、类的符号引用等。
在Java虚拟机中,符号引用(Symbolic Reference)是一种用来表示对类、方法、字段等符号的引用的数据类型。它是一种字面量形式的引用,与直接引用(Direct Reference)相对。
符号引用包含了以下信息:
| 类的全限定名(Fully Qualified Name) | 用于唯一标识一个类 | com.example.MyClass |
| 方法的名称和描述符 | 用于唯一标识一个方法,包括方法名和参数类型列表 | doSomething(int, String) |
| 字段的名称和描述符 | 用于唯一标识一个字段,包括字段名和字段类型 | count:int |
符号引用是在编译期生成的,它是一种与具体内存地址无关的引用。在虚拟机执行时,需要将符号引用解析为直接引用,即将符号引用转化为内存中的实际指针或偏移量。
通过符号引用,虚拟机可以在运行时动态地加载、解析和初始化类,以及调用类的方法和访问类的字段。符号引用的解析过程是虚拟机对类的动态链接的一部分。
需要注意的是,符号引用是在编译期确定的,而直接引用是在运行时确定的。符号引用是一种抽象的引用,而直接引用是具体的内存地址或偏移量。虚拟机在执行时会将符号引用解析为直接引用,以便进行具体的操作。
总的来说,符号引用是一种用于表示对类、方法、字段等符号的引用的数据类型,它包含了类的全限定名、方法的名称和描述符、字段的名称和描述符等信息。虚拟机在执行时会将符号引用解析为直接引用,以便进行具体的操作。
2.4、虚拟机栈(Java Virtual Machine Stacks)
Java 虚拟机栈(Java Virtual Machine Stacks)是专门用于存储每个线程运行时的栈帧。每个 Java 线程在创建时都会创建自己的 Jvm 栈。这些栈在 Jvm 规范中被描述为线程私有的内存区域。

栈帧(Stack Frame):栈帧是 Jvm 栈的基本单位,每个栈帧对应着一个方法调用:
- 局部变量表(Local Variable Table):存储所有局部变量,包括各种基本数据类型、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)。局部变量表的大小在编译期间确定,因此是固定的。
- 操作数栈(Operand Stack):一个 LIFO(后进先出)的栈,用于存储操作指令过程中的各种操作数。比如,在执行算术运算或调用方法时,操作数栈用于传递参数和存储返回结果。
- 动态链接(Dynamic Linking):每个栈帧内部都包含对运行时常量池的引用,以支持方法调用期间的动态链接。
- 方法返回地址(Return Address):当一个方法被调用时,需要知道返回到调用者的位置。这个信息被存储在栈帧中。
虚拟机栈的入栈和出栈操作主要与方法的调用和返回有关。这些操作围绕栈帧(Stack Frame)进行,每个栈帧代表一个方法的调用环境。
以下是虚拟机栈的入栈和出栈过程的详细解释:
入栈(Push):当一个方法被调用时,一个新的栈帧被创建并入栈。这个过程包括以下步骤:
- 栈帧创建:对于每次方法调用,Jvm 创建一个新的栈帧。栈帧包含局部变量表、操作数栈、动态链接信息和方法返回地址等。
- 局部变量初始化:在栈帧中,局部变量表被初始化。对于实例方法,
this引用被存储在局部变量表的第一个位置。方法的参数按顺序被存储在局部变量表中。 - 压入虚拟机栈:创建的栈帧被压入当前线程的虚拟机栈中。Jvm 栈指针移动,指向新的栈顶。
出栈(Pop):当一个方法完成执行后,它的栈帧需要从虚拟机栈中出栈。这个过程包括:
- 方法结束:方法可以通过正常完成或抛出异常来结束。如果方法有返回值,该值被压入调用者的操作数栈中。
- 栈帧弹出:方法完成后,当前栈帧被从虚拟机栈中弹出。Jvm 栈指针回退,指向前一个栈帧。
- 返回值处理(如果有):如果方法有返回值,这个值会在栈帧被弹出后留在调用者的操作数栈顶部。调用者可以从其操作数栈中获取这个返回值。
此外虚拟机栈存在着两种主要的异常情况;
- 栈溢出(
StackOverflowError);如果方法调用的深度过大,超过了 Jvm 栈的最大深度,会发生栈溢出。 - 内存不足(
OutOfMemoryError):如果 Jvm 栈无法获得足够的内存,可能会发生内存不足错误。
2.5、本地方法栈(Native Method Stack)
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如 Sun HotSpo虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
2.6、程序计数器(Program Counter Register)
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。
- 为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为"线程私有"的内存;
- 如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
- 如果正在执行的是 Native 方法,这个计数器值则为空(Undefined);
- 程序计数器 是在 Jvm 规范中,唯一一个没有规定任何 OutOfMemoryError 情况的区域(只存下一个字节码指令的地址,消耗内存小且固定,无论方法多深,它只存一条)。
2.7、直接内存(Direct Memory)
直接内存(Direct Memory)在 Java 虚拟机(Jvm)的上下文中指的是一块不是由 Jvm 直接管理的内存区域,其主要通过 Native 方法直接分配在操作系统的物理内存上。以下是直接内存的一些重要特点和用途:
特点:
- 非 Jvm 堆内存:直接内存不是 Jvm 堆内存的一部分,因此其大小不受 Jvm 最大堆大小设置(通过
-Xmx)的限制。 - 高效数据处理:直接内存访问通常比 Jvm 堆内存访问更高效,因为它避免了 Jvm 堆和 Native 堆之间的数据复制。
- 由操作系统直接管理:这部分内存的分配和回收是由操作系统直接管理的,不受 Jvm 垃圾回收器的影响。
- 手动管理:使用直接内存通常需要显式地分配和释放内存,这增加了编程的复杂性。
用途:
- NIO(New Input/Output):Java NIO 的一大特性是能够使用直接内存进行高效的 I/O 操作。通过将数据存储在直接内存中,可以减少在 Java 堆内存和 Native 内存之间复制数据的次数,从而提高 I/O 操作的效率。
- 大型缓存需求:对于需要大量内存且要求高效访问的场景(如某些缓存机制),直接内存是一个理想的选择。
可以使用 ByteBuffer.allocateDirect(int capacity) 方法来创建一个直接缓冲区,这将在直接内存中分配空间。以下是一个示例:
import java.nio.ByteBuffer;public class DirectMemoryCache {public static void main(String[] args) {// 分配直接内存int capacity = 1024 * 1024 * 10; // 10MBByteBuffer directBuffer = ByteBuffer.allocateDirect(capacity);// 使用直接内存缓存// 例如,写入数据directBuffer.put((byte) 123);// 在后续操作中读取数据directBuffer.flip(); // 切换为读模式byte b = directBuffer.get();// 清理缓存以待再次使用directBuffer.clear();}
}
注意事项:
- 内存泄漏风险:直接内存的分配和释放完全在开发者的控制之下,不当的管理可能导致内存泄漏。
- 内存溢出:如果分配过多的直接内存,超过了物理内存的限制,可能会导致
OutOfMemoryError或者系统的内存溢出。 - 性能考虑:虽然直接内存访问速度快,但其分配和释放的成本比 Jvm 堆内存要高。因此,在不需要高效 I/O 的情况下,使用 Jvm 堆内存可能更合适。
总结:直接内存提供了一种在 Java 中使用高效内存的方式,特别适合于需要快速、大量 I/O 操作的应用程序。然而,它的使用需要仔细考量,以避免内存泄漏和其他内存管理问题。直接内存的使用是一种权衡,需要在性能提升和编程复杂度、资源管理之间找到平衡点。
3、相关参数设定
Java 运行时内存结构可以通过多种 Jvm(Java 虚拟机)参数进行配置和优化。这些参数允许开发者调整内存大小、控制垃圾回收行为等,以适应不同的应用需求和提升性能。以下是一些常用的 Jvm 内存相关参数:
3.1、堆内存设置
- 初始堆大小
-Xms:设置 JVM 启动时堆的初始内存大小。例如,-Xms256m设置初始堆大小为 256MB。 - 最大堆大小
-Xmx:设置 JVM 可以使用的最大堆内存大小。例如,-Xmx1024m设置最大堆大小为 1024MB。 - 新生代大小
-Xmn:设置年轻代的大小。这个值可以影响老年代的大小,因为整个堆大小是固定的。
3.2、元空间(Metaspace)设置
- 元空间初始大小
-XX:MetaspaceSize:设置元空间的初始大小。例如,-XX:MetaspaceSize=128m设置元空间的初始大小为 128MB。 - 元空间最大大小
-XX:MaxMetaspaceSize:设置元空间的最大大小。默认情况下,元空间大小不限制。
3.3、垃圾回收器设置
- 选择垃圾回收器
-XX:+UseG1GC-XX:+UseParallelGC等:选择使用特定的垃圾回收器。不同的垃圾回收器对内存管理策略有不同的影响。
3.4、直接内存设置
- 最大直接内存大小
-XX:MaxDirectMemorySize:设置直接内存的最大大小。这对于使用 NIO 进行 I/O 操作的应用程序特别重要。
3.5、其他性能调优参数
- 堆内存占用率阈值
-XX:MinHeapFreeRatio和-XX:MaxHeapFreeRatio:设置堆空闲空间的最小和最大百分比。JVM 将在这些阈值内动态调整堆的大小。 - 垃圾收集日志
-verbose:gc,-XX:+PrintGCDetails,-XX:+PrintGCTimeStamps等:启用和配置垃圾回收日志,以监控和调试垃圾收集行为。
注意事项:这些参数会根据不同的 Jvm 实现(如 HotSpot、OpenJ9)和版本有所不同。使用这些参数时,需要根据应用程序的具体需求和资源限制进行调整和优化。
调整 Jvm 参数是一个需要考虑多方面因素的过程,需要根据应用程序的行为、垃圾回收日志、性能指标等信息进行综合分析和调优。
相关文章:
Java虚拟机:运行时内存结构
大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 035 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进…...
微信小程序子组件调用父组件的方法
来源:通义千文2.5 步骤 1: 定义父组件中的方法 首先,在父组件中定义一个方法(如 handleClick),并准备一个用于接收子组件传来的数据的方法。 父组件(Parent.wxml) html<!-- parent.wxml …...
【数据结构】TreeMap和TreeSet
目录 前言TreeMap实现的接口内部类常用方法 TreeSet实现的接口常用方法 前言 Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。 一般把搜索的数据称为关键字(Key), 和关键字对应的称为…...
前端react集成OIDC
文章目录 OpenID Connect (OIDC)3种 授权模式 【服务端】express 集成OIDC【前端】react 集成OIDCoidc-client-js库 原生集成react-oidc-context 库非组件获取user信息 OAuth 2.0 协议主要用于资源授权。 OpenID Connect (OIDC) https://openid.net/specs/openid-connect-core…...
JavaWeb—XML_Tomcat10_HTTP
一、XML XML是EXtensible MarkupLanguage的缩写,翻译过来就是可扩展标记语言。所以很明显,XML和HTML一样都是标记语言,也就是说它们的基本语法都是标签。 可扩展:三个字表面上的意思是XML允许自定义格式。但这不代表你可以随便写; 在XML基…...
中介者模式在Java中的实现:设计模式精解
中介者模式在Java中的实现:设计模式精解 中介者模式(Mediator Pattern)是一种行为型设计模式,用于定义一个中介者对象,以封装一系列对象之间的交互,从而使对象之间的交互不再直接发生,减少了系…...
PyQt编程快速上手
Python GUI安装 GUI就是图形用户界面的意思,在Python中使用PyQt可以快速搭建自己的应用,使得自己的程序看上去更加高大上,学会GUI编程可以使得自己的软件有可视化的结果。 如果你想用Python快速制作界面,可以安装PyQt:…...
Docker Swarm管理
Docker Swarm管理 前置知识点 Docker Swarm 是 Docker 公司 2014年出品的基于 Docker 的集群管理调度工具,能够将多台主机构建成一个Docker集群,并结合Overlay网络实现容器调度的互访 用户可以只通过 Swarm API 来管理多个主机上的 Docker Swarm 群集包…...
Python | Leetcode Python题解之第335题路径交叉
题目: 题解: class Solution:def isSelfCrossing(self, distance: List[int]) -> bool:n len(distance)# 处理第 1 种情况i 0while i < n and (i < 2 or distance[i] > distance[i - 2]):i 1if i n:return False# 处理第 j 次移动的情况…...
Ubuntu视频工具
1. VLC VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影音光…...
HBase snapshot+replication 测试
一、背景 画像标签服务(CDP)是核心服务,被公司其他系统如现金、电商、风控等核心业务调用。异常的话,影响范围大。 二、目标 存量数据测试通过 snapshot 迁移。增量数据测试通过 replication 同步。 三、测试 方案二测试&#x…...
代码随想录算法训练营第四十一天|图论基础、深度优先搜索理论基础、98. 所有可达路径、797. 所有可能的路径
图论基础 图的种类:有向图 和 无向图,加权有向图, 加权无向图 无向图中有几条边连接该节点,该节点就有几度。 在有向图中,每个节点有出度和入度。出度:从该节点出发的边的个数。入度:指向该节…...
STM32学习笔记09-SPI通信
目录 SPI通信简介 硬件电路 移位示意图 SPI基本时序单元 SPI时序 W25Q64简介 硬件电路 W25Q64框图 Flash操作注意事项 SPI外设简介 SPI框图 SPI基本结构 主模式全双工连续传输 非连续传输 软件/硬件波形对比 SPI应用 软件SPI读写W25Q64 硬件SPI读写W25Q64 SP…...
树------二叉树
什么是树: 树是一种特殊的结构,由多个节点连接构成,并且不包含回路,也可以认为树是不包含回路的无向连通图,具体如下图所示。 当我们要确定一棵树的形态时,要指定一个根节点,没有父亲节点的节点…...
如何对加密后的数据进行模糊查询(面试题)
目录 前言1. 基本知识2. 国内做法 前言 这道题在面试比较常见,但是在算法逻辑层面中,直接对加密数据进行模糊查询是不可行的,因为加密算法会使数据变成不可读的形式 需要在加密过程中采取特殊的策略来支持模糊查询 以下只是结合网上现有的资…...
【MYSQL】当前读和快照读
前言 复习下隔离级别: 1、读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。 2、读提交:一个事务提交之后,它做的变更会被其他事务看到 3、可重复读:一个事务执行过程中看到的数据,…...
C语言-使用数组法,指针法实现将一个5X5的矩阵中最大的元素放在中心,四个角分别放四个最小的元素(顺序为从左到右,从上到下,从小到大存放),写一函数实现之。
1.题目要求: 将一个5X5的矩阵中最大的元素放在中心,四个角分别放四个最小的元素(顺序为从左到右,从上到下,从小到大存放),写一函数实现之。 2.数组法实现 #define _CRT_SECURE_NO_WARNINGS 1…...
Android gradle 构建
Understanding Tasks - Gradle task kapt 是 Kotlin 语言的注解处理器,它是 Android Studio 中用于处理 Kotlin 注解的工具。它通过在编译期间生成代码来增强 Kotlin 代码的功能。需要 Kotlin 编译器来解析和处理注解;使用 APT 来生成代码,…...
vulnhub系列:devguru
vulnhub系列:devguru 靶机下载 一、信息收集 nmap扫描存活,根据mac地址寻找IP nmap 192.168.23.0/24nmap扫描端口,开放端口:22、80、8585 nmap 192.168.23.147 -p- -sV -Pn -O访问80端口 dirb目录扫描,存在 git 源…...
Robot Operating System——高质量图像传输
大纲 应用场景定义字段解释 案例 sensor_msgs::msg::Image 是 ROS (Robot Operating System) 中的一个消息类型,用于表示未压缩的图像数据。它通常用于传输和处理高质量的图像数据。 应用场景 机器人视觉 图像处理:在机器人视觉系统中,未压缩…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...
图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...
车载诊断架构 --- ZEVonUDS(J1979-3)简介第一篇
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…...
Linux入门课的思维导图
耗时两周,终于把慕课网上的Linux的基础入门课实操、总结完了! 第一次以Blog的形式做学习记录,过程很有意思,但也很耗时。 课程时长5h,涉及到很多专有名词,要去逐个查找,以前接触过的概念因为时…...
篇章一 论坛系统——前置知识
目录 1.软件开发 1.1 软件的生命周期 1.2 面向对象 1.3 CS、BS架构 1.CS架构编辑 2.BS架构 1.4 软件需求 1.需求分类 2.需求获取 1.5 需求分析 1. 工作内容 1.6 面向对象分析 1.OOA的任务 2.统一建模语言UML 3. 用例模型 3.1 用例图的元素 3.2 建立用例模型 …...
基于Java的离散数学题库系统设计与实现:附完整源码与论文
JAVASQL离散数学题库管理系统 一、系统概述 本系统采用Java Swing开发桌面应用,结合SQL Server数据库实现离散数学题库的高效管理。系统支持题型分类(选择题、填空题、判断题等)、难度分级、知识点关联,并提供智能组卷、在线测试…...
成工fpga(知识星球号)——精品来袭
(如需要相关的工程文件请关注知识星球:成工fpga,https://t.zsxq.com/DMeqH,关注即送200GB学习资料,链接已置顶!) 《孩子都能学会的FPGA》系列是成工完成的第一个系列,也有一年多的时…...
RMQ 算法详解(区间最值问题)
RMQ 算法详解(区间最值问题) 问题介绍解决方法暴力法ST表法基本思想算法步骤C实现 问题介绍 RMQ问题是OI中经常遇到的问题,主要是一下形式: 给你一堆数,不断的对里面的数进行操作,例如:让某个…...
