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

【Java JVM】栈帧

执行引擎是 Java 虚拟机核心的组成部分之一。 在《Java虚拟机规范》中制定了 Java 虚拟机字节码执行引擎的概念模型,
这个概念模型成为各大发行商的 Java 虚拟机执行引擎的统一外观 (Facade)。

不同的虚拟机的实现中, 通常会有

  1. 解释执行 (通过解释器执行)
  2. 编译执行 (通过即时编译器产生本地代码执行)
    两种选择, 也可能两者兼备, 还可能会有同时包含几个不同级别的即时编译器一起工作的执行引擎。
    但是从外观上看, 所有的 Java 虚拟机的执行引擎都是: 输入的是字节码二进制流, 输出的是执行结果

而这一个的过程, 落实到 HotSpot 的实现就是运行时栈帧
下面会对运行时栈帧做一个简单的介绍。

1 运行时栈帧

Java 虚拟机以方法作为最基本的执行单元, “栈帧” (Stack Frame) 则是用于支持虚拟机进行方法调用和方法执行背后的数据结构, 它也是虚拟机运行时数
据区中的虚拟机栈 (Virtual Machine Stack) 的栈元素。

栈帧存储了方法的局部变量表 + 操作数栈 + 动态连接 + 方法返回地址等信息。
每一个方法从调用开始至执行结束的过程, 都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。

在编译 Java 程序源码的时候, 栈帧中需要多大的局部变量表, 需要多深的操作数栈就已经被分析计算出来, 并且写入到方法表的 Code 属性之中。
一个栈帧需要分配多少内存, 并不会受到程序运行期变量数据的影响, 而仅仅取决于程序源码和具体的虚拟机实现的栈内存布局形式。

一个线程中的方法调用链可能会很长, 以 Java 程序的角度来看, 同一时刻, 同一条线程里面, 在调用堆栈的所有方法都同时处于执行状态。而对于执行引擎
来讲, 在活动线程中, 只有位于栈顶的方法才是在运行的, 只有位于栈顶的栈帧才是生效的, 其被称为 “当前栈帧” (Current Stack Frame), 与这个栈帧
所关联的方法被称为 “当前方法” (Current Method)。

综上, 栈帧的结构如下:
Alt '栈帧结构'

2 局部变量表 - Local Variables Table

局部变量表是一组变量值的存储空间, 用于存放方法参数和方法内部定义的局部变量。在 Java 程序被编译为 Class 文件时, 就在方法的 Code 属性的
max_locals 数据项中确定了该方法所需分配的局部变量表的最大容量。

局部变量表的容量以变量槽 (Variable Slot) 为最小单位。1 个槽的具体大小, 《Java虚拟机规范》中并没有明确指出, 但是引导性地说明了

  1. boolean
  2. byte
  3. char
  4. short
  5. int
  6. float
  7. reference
  8. returnAddress 类型的数据
    这 8 种数据类型, 都可以使用 32 位或更小的物理内存来存储。

reference 类型表示对一个对象实例的引用, 《Java虚拟机规范》既没有说明它的长度, 也没有明确指出这种引用应有怎样的结构。
但是需要能通过这个引用做到两件事情

  1. 从根据引用直接或间接地查找到对象在 Java 堆中的数据存放的起始地址或索引
  2. 根据引用直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息

returnAddress 类型模板很少见了, 它是为字节码指令 jsr, jsr_w 和 ret 服务的, 指向了一条字节码指令的地址, 某些很古老的 Java 虚拟机曾
经使用这几条指令来实现异常处理时的跳转, 但现在也已经全部改为采用异常表来代替了。

对于 64 位的数据类型, Java 虚拟机会以高位对齐的方式为其分配两个连续的变量槽空间。Java 语言中明确的 64 位的数据类型只有 long 和 double 两种。

Java 虚拟机通过索引定位的方式使用局部变量表, 索引值的范围是从 0 开始至局部变量表最大的变量槽数量。如果访问的是 32 位数据类型的变量, 索引 N
就代表了使用第 N 个变量槽, 如果访问的是 64 位数据类型的变量, 则说明会同时使用第 N 和 N + 1 两个变量槽。 对于两个相邻的共同存放一个 64 位数
据的两个变量槽, 虚拟机不允许采用任何方式单独访问其中的某一个。

当一个方法被调用时, Java 虚拟机会使用局部变量表来完成参数值到参数变量列表的传递过程, 即实参到形参的传递。 如果执行的是实例方法 (没有被 static
修饰的方法), 那局部变量表中第 0 位索引的变量槽默认是用于传递方法所属对象实例的引用, 在方法中可以通过关键字 “this” 来访问到这个隐含的参数。
其余参数则按照参数表顺序排列, 占用从 1 开始的局部变量槽, 参数表分配完毕后, 再根据方法体内部定义的变量顺序和作用域分配其余的变量槽。

为了尽可能节省栈帧耗用的内存空间, 局部变量表中的变量槽是可以重用的, 方法体中定义的变量, 其作用域并不一定会覆盖整个方法体, 如果当前字节码 PC
计数器的值已经超出了某个变量的作用域, 那这个变量对应的变量槽就可以交给其他变量来重用。 不过, 这样的设计除了节省栈帧空间以外, 还会伴随有少量
额外的副作用, 例如在某些情况下变量槽的复用会直接影响到系统的垃圾收集行为

注: 在虚拟机运行参数中加上 “-verbose:gc” 来看看垃圾收集的过程。

public static void main(String[] args)() { byte[] placeholder = new byte[64 * 1024 * 1024]; System.gc(); 
}

输出结果:

[GC (System.gc())  76402K->67232K(188416K), 0.0062015 secs]
[Full GC (System.gc())  67232K->67164K(188416K), 0.0253018 secs]

发现在 System.gc() 运行后并没有回收掉这 64MB 的内存, 因为在执行 System.gc() 时, 变量 placeholder 还处于作用域之内, 虚拟机自然不敢回收掉 placeholder 的内存。

修改一下代码

public static void main(String[] args)() {if (1 == 1) {byte[] placeholder = new byte[64 * 1024 * 1024];}System.gc(); 
}

输出结果:

[GC (System.gc())  76402K->67232K(188416K), 0.0062015 secs]
[Full GC (System.gc())  67232K->67164K(188416K), 0.0253018 secs]

在 if 判断结束后, placeholder 的作用域被限制在花括号以内, 从代码逻辑上讲, 在执行 System.gc() 的时候, placeholder 已经不可能再被访问了, 但执行这段程序 gc 后, 64MB 的内存还是没释放。

再做一下修改

public static void main(String[] args) {if (1 == 1) {byte[] placeholder = new byte[64 * 1024 * 1024];}int a = 12;System.gc();
}

这次的输出结果:

[GC (System.gc())  76402K->67232K(188416K), 0.0038170 secs]
[Full GC (System.gc())  67232K->1629K(188416K), 0.0189803 secs]

placeholder 能否被回收的根本原因就是: 局部变量表中的变量槽是否还存有关于 placeholder 数组对象的引用。 第一次修改中, 代码虽然已经离开了 placeholder 的作用域, 但在此之后, 再没有发生过任何对局部变量表的读写操作, placeholder 原本所占用的变量槽还没有被其他变量所复用。 所以作为 GC Roots 一部分的局部变量表仍然保持着对它的关联。 这种关联没有被及时打断, 绝大部分情况下影响都很轻微。 但如果遇到一个方法, 其后面的代码有一些耗时很长的操作, 而前面又定义了占用了大量内存但实际上已经不会再使用的变量, 手动将其设置为 null 值 (用来代替那句 int
a=12, 把变量对应的局部变量槽清空) 便不见得是一个绝对无意义的操作, 这种操作可以作为一种在极特殊情形 (对象占用内存大, 此方法的栈帧长时间不能被回收, 方法调用次数达不到即时编译器的编译条件) 下的 “奇技” 来使用。

我们知道在类的加载阶段, 对于类的属性值, 会存在 2 次赋值, 第一次在准备阶段, 基于了字段的默认值, 第二次基于字段真正的数值。 但是在局部变量中, 如果只声明了, 但是没有赋值, 这个字段是没法使用的, 因为其不存在默认值。

3 操作数栈 - Operand Stack

操作数栈也常被称为操作栈, 是一个后入先出 (Last In First Out, LIFO) 的栈。
操作数栈的最大深度也在编译的时候被写入到 Code 属性的 max_stacks 数据项之中。 操作数栈的每一个元素都可以是包括 long 和 double 在内的任意 Java 数据类型。 32 位数据类型所占的栈容量为 1, 64 位数据类型所占的栈容量为 2。 当一个方法刚刚开始执行的时候, 这个方法的操作数栈是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈和入栈操作。 例如: 整数加法的字节码指令 iadd, 会从操作数栈的顶部出去 2 个数, 相加后, 在存入栈顶。

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配。 每个字节码都有其操作的数据类型限制。 这个限制编译程序代码的时候, 编译器会进行保证, 在类校验阶段的数据流分析中还会再次验证这一点。

在概念模型中, 两个不同栈帧作为不同方法的虚拟机栈的元素, 是完全相互独立的。 但是在大多虚拟机的实现里都会进行一些优化处理, 令两个栈帧出现一部分重叠。 让下面栈帧的部分操作数 栈与上面栈帧的部分局部变量表重叠在一起, 这样做不仅节约了一些空间, 更重要的是在进行方法调 用时就可以直接共用一部分数据, 无须进行额外的参数复制传递了。

Alt '栈帧真实实现'

  • 操作数栈 + 局部变量表配合的流程
public class Application {public static void main(String[] args) throws InterruptedException {int add = add(1, 2);System.out.println(add);}public static int add(int num1, int num2) {num1 = 3;int result = num1 + num2;return result;}
}

先通过 javac Application.java
得到字节码文件, 再通过 javap -c -l -p -v Application.class >> class.txt

打开 class.txt 就能得到我们的字节码文件了, 内容如下

Compiled from "Application.java"
public class Application {public Application();Code:0: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]) throws java.lang.InterruptedException;Code:0: iconst_11: iconst_22: invokestatic  #2                  // Method add:(II)I5: istore_16: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;9: iload_110: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V13: returnpublic static int add(int, int);Code:0: iconst_31: istore_02: iload_03: iload_14: iadd5: istore_26: iload_27: ireturn
}

整理成图的话, 调用 add 方法前是这样的

Alt '栈帧处理过程-步骤1'

  • 先看一下 add 方法的局部变量

首先我们看到 add 方法的局部变量表有三个属性, 分别是入参的 num1, num2, 还有方法内部声明的 result 变量。

补充说明: 因为这里的 add 方法是静态的, 所以入参有几个变量, 那么局部变量表就有几个。
在实际中, 如果方法是非静态的话, 局部变量变的第一个会多 1 个 this 的变量, 后面才是入参。

  • add 方法的操作数栈的变化

各个指令的作用可以通过查看官网得知: 地址

add 方法

  1. 第一个指令 iconst_3, 把一个 int 的数字放入到操作栈, 这里的 iconst_3, 代表的是把 3 放入到操作栈 (注意这里的 iconst_? 只能表示到 5,
    超过 5 后的指令会变成 bipush 数字)

这时候的操作数栈是这样的
Alt '栈帧处理过程-步骤2'

  1. 第二个指令 istore_0, 把操作数栈, 最上面的数字 (必须是 int) 出栈, 并覆盖局部变量表第 0 的位置

这时候的操作数栈是这样的
Alt '栈帧处理过程-步骤3'

通过查看局部变量表的变化, 我们可以知道 num1 = 3 在程序中是分成了 2 个指令完成的

  1. 第三个指令 iload_0, 把第 0 个局部变量的值放入栈帧(这个值必须是 int 类型)
    第四个命令为 iload_1, 那么就是把第 1 个局部变量放入到栈帧

经过第三, 四个指令后, 这时候的操作数栈是这样的
Alt '栈帧处理过程-步骤4'

  1. 第五个指令 iadd, 把栈帧中的头 2 个 int 操作数弹出来, 进行相加, 然后再把结果入栈。

经过第五个指令后, 这时候的操作数栈是这样的
Alt '栈帧处理过程-步骤5'

后面的六, 七 就省略了

ireturn 从纸面上就能里面了吧, 把操作数栈中的栈顶(一个 int 的数字)返回出去。

从上面的内容, 可以知道, 操作数栈用于方法执行过程的流转, 局部变量表则用于存储临时的变量数据。

4 动态连接 - Dynamic Linking

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有这个引用是为了支持方法调用过程中的动态连接。
Class 文件的常量池中存有大量的符号引用, 字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数 (invokevirtual #4 // Method java/io/PrintStream.println:(I)V)
这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用, 这种转化被称为静态解析。 另外一部分将在每一次运行期间都转化为直接引用,
这部分就称为动态连接。

5 方法返回地址 - return address

当一个方法开始执行后, 只有两种方式退出这个方法。

第一种方式是执行引擎遇到任意一个方法返回的字节码指令, 这时候可能会有返回值传递给上层的方法调用者 (调用当前方法的方法称为调用者或者主调方法),
方法是否有返回值以及返回值的类型将根据遇到何种方法返回指令来决定, 这种退出方法的方式称为 “正常调用完成” (Normal Method Invocation Completion)

第二种退出方式是在方法执行的过程中遇到了异常, 并且这个异常没有在方法体内得到妥善处理。无论是 Java 虚拟机内部产生的异常, 还是代码中使用 athrow
字节码指令产生的异常。只要在本方法的异常表中没有搜索到匹配的异常处理器, 就会导致方法退出, 这种退出方法的方式称为 “异常调用完成 (Abrupt Method Invocation Completion)”。
一个方法使用异常完成出口的方式退出, 是不会给它的上层调用者提供任何返回值的。

无论采用何种退出方式, 在方法退出之后, 都必须返回到最初方法被调用时的位置, 程序才能继续执行。
方法返回时可能需要在栈帧中保存一些信息, 用来帮助恢复它的上层主调方法的执行状态。
一般来说, 方法正常退出时, 主调方法的 PC 计数器的值就可以作为返回地址, 栈帧中很可能会保存这个计数器值。而方法异常退出时, 返回地址是要通过异常
处理器表来确定的, 栈帧中就一般不会保存这部分信息。

方法退出的过程实际上等同于把当前栈帧出栈, 因此退出时可能执行的操作有: 恢复上层方法的局部变量表和操作数栈, 把返回值 (如果有的话) 压入调用者栈
帧的操作数栈中, 调整 PC 计数器的值以指向方法调用指令后面的一条指令等。

6 附加信息 - overhead information

《Java虚拟机规范》允许虚拟机实现增加一些规范里没有描述的信息到栈帧之中, 例如与调试, 性能收集相关的信息, 这部分信息完全取决于具体的虚拟机实现, 这里不再详述。

7 参考

《深入理解Java虚拟机》- 周志明

相关文章:

【Java JVM】栈帧

执行引擎是 Java 虚拟机核心的组成部分之一。 在《Java虚拟机规范》中制定了 Java 虚拟机字节码执行引擎的概念模型, 这个概念模型成为各大发行商的 Java 虚拟机执行引擎的统一外观 (Facade)。 不同的虚拟机的实现中, 通常会有 解释执行 (通过解释器执行)编译执行 (通过即时编…...

【Emgu CV教程】5.4、几何变换之图像翻转

今天讲解的两个函数&#xff0c;可以实现以下样式的翻转。 水平翻转&#xff1a;将图像沿Y轴(图像最左侧垂直边缘)翻转的操作。原始图像中位于左侧的内容将移动到目标图像的右侧&#xff0c;原始图像中位于右侧的内容将移动到目标图像的左侧。垂直翻转&#xff1a;将图像沿X轴…...

2024年AMC8历年真题练一练和答案详解(10),以及全真模拟题

六分成长继续为您分享AMC8历年真题&#xff0c;最后两天通过高质量的真题来体会快速思考、做对题目的策略。 题目从575道在线题库&#xff08;来自于往年真题&#xff09;中抽取5道题&#xff0c;每道题目均会标记出自年份和当年度的序号&#xff0c;并附上详细解析。【使用六…...

echarts业务中常用属性设置记录

1.legend计算占比 //在data中定义两个字段 total:0, znum:0 //计算上面两个值 this.data.forEach(val > this.total parseInt(val.value)); for (let i 0; i < nv.length; i) {if (i ! nv.length - 1) {this.znum this.znum Number(parseFloat((nv[i].value / this.t…...

Ubuntu 22.04 安装prometheus

服务器监控和报警软件有很多&#xff0c;为什么我们会选择Prometheus而不是其他软件呢&#xff1f; 因为它有以下优点&#xff1a; 自带简易web监控页面&#xff0c;用户可以很方便地查看监控数据和使用仪表盘。能实时收集数据并根据自定义警报规则推送告警&#xff1b;具有丰…...

Django的模板语言

文章目录 模板语法变量标签过滤器注释 组件引擎模板上下文加载器上下文处理器 模板引擎的支持配置用法引擎内置后端 模板 作为一个网络框架&#xff0c;Django 需要一种方便的方式来动态生成 HTML。最常见的方法是依靠模板。一个模板包含了所需 HTML 输出的静态部分&#xff0…...

为什么安卓逆向手机要root

安卓逆向工程是指对安卓应用程序进行研究和分析&#xff0c;以了解其内部工作原理、提取资源、修改应用行为、发现漏洞等。在某些情况下&#xff0c;为了进行逆向分析&#xff0c;需要对手机进行Root。 以下是一些安卓逆向中可能需要Root的原因&#xff1a; 获得完全访问权限…...

整合junit与热部署

整合junit <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.7.0</version></dependency> 测试类上添加SpringBootTest 如&#xff1a; 注意测试类的…...

C++面试宝典第21题:字符串解码

题目 给定一个经过编码的字符串,返回其解码后的字符串。具体的编码规则为:k[encoded_string],表示方括号内部的encoded_string正好重复k次。注意:k保证为正整数;encoded_string只包含大小写字母,不包含空格和数字;方括号确定是匹配的,且可以嵌套。 示例: 编码字符串为…...

WXUI 基于uni-app x开发的高性能混合UI库

uni-app x 是什么&#xff1f; uni-app x&#xff0c;是下一代 uni-app&#xff0c;是一个跨平台应用开发引擎。 uni-app x 没有使用js和webview&#xff0c;它基于 uts 语言。在App端&#xff0c;uts在iOS编译为swift、在Android编译为kotlin&#xff0c;完全达到了原生应用…...

P9840 [ICPC2021 Nanjing R] Oops, It‘s Yesterday Twice More题解

[ICPC2021 Nanjing R] Oops, It’s Yesterday Twice More 传送门 题面翻译 有一张 n n n\times n nn 的网格图&#xff0c;每个格子上都有一只袋鼠。对于一只在 ( i , j ) (i,j) (i,j) 的袋鼠&#xff0c;有下面四个按钮&#xff1a; 按钮 U&#xff1a;如果 i > 1 …...

OceanBase与MySQL兼容性对比

OB针对于高并发和大数据更有优势&#xff0c;公司的dba让我们把数据从mysql迁移到OceanBase了&#xff0c;这里记录一下OceanBase的MySQL模式。 OceanBase的MySQL模式兼容MySQL5.7的绝大部分功能和语法,兼容MySQL5.7版本的全量以及8.0版本的部分JSON函数。 暂不支持的功能: O…...

【linux】visudo

碎碎念 visudo命令是用来修改一个叫做 /etc/sudoers 的文件的&#xff0c;用来设置哪些 用户 和 组 可以使用sudo命令。并且使用visudo而不是使用 vi /etc/sudoers 的原因在于&#xff1a;visudo自带了检查功能&#xff0c;可以判断是否存在语法问题&#xff0c;所以更加安全 …...

Nvidia-docker的基础使用方法

安装&#xff1a; 安装nvidia-docker&#xff1a; distribution$(. /etc/os-release;echo $ID$VERSION_ID)curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.l…...

uniapp一键换色

需求 : 在我们现有项目基础上, 把原来的颜色替换成另一个颜色, 同时需要为下一个项目预留出来随时更换主题色, 实现一键换色 实现 : 1. 介绍 兼容不同项目对主题色及图标的需求 主要通过以下对css颜色和icon主题色图标两个模块的切换 scss/less的css变量config/index.js中的…...

动态规划思想案例刨析

动态规划的思想 动态规划解决问题的核心思想是“重叠子问题”和“最优子结构”。 重叠子问题&#xff1a;在复杂问题中&#xff0c;往往存在许多重复的子问题。动态规划通过避免重复计算&#xff0c;将子问题的解保存起来&#xff0c;以便在需要时直接引用&#xff0c;从而提…...

vtk9.3 配置 visual studio 2019 运行环境 和运行实例详解

&#xff08;1&#xff09;包含文件配置&#xff1a; 项目--属性--VC目录&#xff0c;在包含目录中把include文件夹的地址加进去&#xff0c;一直要到下一级 vtk-9.3目录下&#xff0c; 小知识&#xff1a; 在Visual Studio 2019中运行项目时&#xff0c;如果项目中使用了第三…...

腾讯云添加SSL证书

一、进入腾讯云SSL证书&#xff1a; ssl证书控制台地址 选择“我的证书”&#xff0c;点击"申请免费证书" 2、填写域名和邮箱&#xff0c;点击“提交申请” 在此页面中会出现主机记录和记录值。 2、进入云解析 DNS&#xff1a;云解析DNS地址 进入我的解析-记录…...

CentOS下用rpm安装软件时报错error: Failed dependencies

在CentOS下用rpm安装软件时会报如下错误&#xff1a; 1、安装时提示&#xff1a; [rootdb software]# rpm -ivh ksh-20120801-254.el8.x86_64.rpm warning: ksh-20120801-254.el8.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID 8483c65d: NOKEY error: Failed depende…...

Vue3+Vite连接高德地图JS API——地图显示、输入搜索

1 开通高德地图Web端JS API服务 1、进入高德地图API官网&#xff08;https://lbs.amap.com/&#xff09;&#xff1a; 2、注册登录。 3、进入控制台。 4、点击“应用管理”&#xff0c;点击“我的应用”&#xff0c;创建新应用。 5、添加Key&#xff0c;服务平台选择“Web端&…...

零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?

一、核心优势&#xff1a;专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发&#xff0c;是一款收费低廉但功能全面的Windows NAS工具&#xff0c;主打“无学习成本部署” 。与其他NAS软件相比&#xff0c;其优势在于&#xff1a; 无需硬件改造&#xff1a;将任意W…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

Linux链表操作全解析

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

srs linux

下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935&#xff0c;SRS管理页面端口是8080&#xff0c;可…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...

高危文件识别的常用算法:原理、应用与企业场景

高危文件识别的常用算法&#xff1a;原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件&#xff0c;如包含恶意代码、敏感数据或欺诈内容的文档&#xff0c;在企业协同办公环境中&#xff08;如Teams、Google Workspace&#xff09;尤为重要。结合大模型技术&…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。

1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj&#xff0c;再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...