JVM字节码指令详解

news2024/10/5 19:14:51

文章目录

  • 前言
  • 一、JVM字节码指令概述
    • 1. 什么是JVM字节码指令:
    • 2. 字节码指令的作用:
    • 3. 字节码指令的分类:
  • 二、字节码指令的种类
    • 1. 加载和存储指令
    • 2. 算术指令
    • 3. 类型转换指令
    • 4. 对象和数组操作指令
    • 5. 操作数栈管理指令
    • 6. 控制转移指令
    • 7. 方法调用和返回指令
    • 8. 异常处理指令
    • 9. 同步指令
    • 总结一下指令命名规律
      • 1. 操作数类型前缀
      • 2. 指令的操作:
      • 3. 指令的粒度:
      • 4. 数组操作相关:
      • 5. 控制转移和方法调用:
  • 三、字节码指令的详细解读
    • 1. JVM如何解析字节码指令
    • 2. JVM如何执行字节码指令
  • 四、字节码工具的使用
    • 1. 如何使用javap查看字节码
    • 2. 如何使用ASM等工具修改字节码
    • 3. 如何使用JIT编译器优化字节码
  • 五、字节码指令的优化策略
    • 1. 常量折叠优化:
    • 2. 代码重排序优化:
    • 3. 常量传播优化:
    • 4. 公共子表达式消除:
    • 5. 死代码消除:
  • 六、字节码指令的实际应用
  • 7.参考文档

前言

在我们日常的Java开发中,我们的IDE开发工具会自动的通过编译器将Java代码编译成字节码文件,最后通过JVM来执行这些字节码文件。在这个过程中,一个重要的环节就是Java字节码。Java字节码是Java虚拟机(JVM)可以执行的指令集,它在一定程度上代表了Java的跨平台特性,因为不同的操作系统平台上的JVM都可以执行相同的Java字节码。因此,深入理解Java字节码对于掌握Java的运行机制是非常重要的。

在本篇博文中,我将对JVM字节码的基础知识、指令类型、命名规则以及优化策略等方面进行详细的讲解。我和大家一起更好地理解JVM的工作原理,同时也能够在实际开发中遇到性能问题时,能够更好地进行问题定位和性能调优。本文假设读者已经有一定的Java基础,并且熟悉基本的计算机原理,例如栈、堆等基本概念。如果你对这些还不是很熟悉,建议先补充相关的基础知识。

一、JVM字节码指令概述

1. 什么是JVM字节码指令:

JVM字节码指令是Java Virtual Machine(JVM)在执行Java程序时所遵循的一种低级指令集。在Java应用程序编译为.class文件后,存储在文件中的就是这些字节码指令。这些指令是在Java虚拟机上运行的,与具体的操作系统和硬件无关,实现了Java语言的“一次编译,到处运行”的特性。

2. 字节码指令的作用:

JVM字节码指令的主要作用是定义了执行Java类或者接口中方法的操作语义,包括如何加载数据,如何存储数据,如何操作数据,如何控制流程等等。同时,字节码还能保证Java程序在不同的平台上都能得到正确且一致的执行效果。

3. 字节码指令的分类:

参考Oracle官网 Chapter 6. The Java Virtual Machine Instruction Set
JVM字节码指令大致可以分为以下几类:

  • 加载指令(Load):用于把变量从主存加载到栈内存。
  • 存储指令(Store):用于把变量从栈内存存储到主存。
  • 栈操作指令(Stack):用于对栈进行操作,比如交换栈顶元素,复制栈顶元素等。
  • 算术指令(Arithmetic):用于进行算术运算,比如加法,减法,乘法,除法等。
  • 转换指令(Conversion):用于进行类型转换,比如将int类型转为float类型等。
  • 比较指令(Comparison):用于进行数值比较。
  • 控制指令(Control):用于进行控制流转移。
  • 引用指令(Reference):用于操作对象,比如创建对象,获取对象的字段等。
  • 扩展指令(Extended):用于对指令集进行扩展。
  • 同步指令(Synchronization):用于线程同步。

二、字节码指令的种类

我们使用一个简单的Java类然后使用javap -c 类名 输出字节码指令,来分析一下

public class HelloByteCode {
    private int num;
    private String str;

    public HelloByteCode(int num, String str) {
        this.num = num;
        this.str = str;
    }

    public int addNum(int add) {
        return this.num + add;
    }

    public String appendStr(String append) {
        return this.str + append;
    }

    public static void main(String[] args) {
        HelloByteCode helloByteCode = new HelloByteCode(10, "Hello");
        int resultNum = helloByteCode.addNum(20);
        String resultStr = helloByteCode.appendStr(" ByteCode");
        System.out.println(resultNum);
        System.out.println(resultStr);
    }
}

使用Java的javap工具输出它的字节码指令。在命令行中输入javap -c HelloByteCode,将得到下面的结果:

public class HelloByteCode {
  // 构造函数
  public HelloByteCode(int, java.lang.String);
    Code:
       0: aload_0                   // 将第一个引用类型本地变量加载到操作数栈顶
       1: invokespecial #1          // 调用超类构造函数,实例初始化
       4: aload_0                   // 将第一个引用类型本地变量加载到操作数栈顶
       5: iload_1                   // 将第二个int类型本地变量加载到操作数栈顶
       6: putfield      #2          // 将栈顶的数值存入到对象的num字段
       9: aload_0                   // 将第一个引用类型本地变量加载到操作数栈顶
      10: aload_2                   // 将第三个引用类型本地变量加载到操作数栈顶
      11: putfield      #3          // 将栈顶的数值存入到对象的str字段
      14: return                    // 返回

  // 方法:加数
  public int addNum(int);
    Code:
       0: aload_0                   // 将第一个引用类型本地变量加载到操作数栈顶
       1: getfield      #2          // 获取对象的num字段值
       4: iload_1                   // 将第二个int类型本地变量加载到操作数栈顶
       5: iadd                      // 执行加法操作
       6: ireturn                   // 返回int值

  // 方法:拼接字符串
  public java.lang.String appendStr(java.lang.String);
    Code:
       0: new           #4          // 创建一个StringBuilder对象
       3: dup                       // 复制栈顶元素并压入栈顶
       4: invokespecial #5          // 调用StringBuilder构造函数,实例初始化
       7: aload_0                   // 将第一个引用类型本地变量加载到操作数栈顶
       8: getfield      #3          // 获取对象的str字段值
      11: invokevirtual #6          // 调用StringBuilder的append方法
      14: aload_1                   // 将第二个引用类型本地变量加载到操作数栈顶
      15: invokevirtual #6          // 调用StringBuilder的append方法
      18: invokevirtual #7          // 调用StringBuilder的toString方法
      21: areturn                   // 返回引用类型值

  // 主函数
  public static void main(java.lang.String[]);
    Code:
       0: new           #8          // 创建一个HelloByteCode对象
       3: dup                       // 复制栈顶元素并压入栈顶
       4: bipush        10          // 将int类型常量10压入栈顶
       6: ldc           #9          // 将String类型常量"Hello"压入栈顶
       8: invokespecial #10         // 调用HelloByteCode构造函数,实例初始化
      11: astore_1                  // 将栈顶引用类型值存入第二个本地变量
      12: aload_1                   // 将第二个引用类型本地变量加载到操作数栈顶
      13: bipush        20          // 将int类型常量20压入栈顶
      15: invokevirtual #11         // 调用HelloByteCode的addNum方法
      18: istore_2                  // 将栈顶int类型值存入第三个本地变量
      19: aload_1                   // 将第二个引用类型本地变量加载到操作数栈顶
      20: ldc           #12         // 将String类型常量" ByteCode"压入栈顶
      22: invokevirtual #13         // 调用HelloByteCode的appendStr方法
      25: astore_3                  // 将栈顶引用类型值存入第四个本地变量
      26: getstatic     #14         // 获取System.out静态字段值
      29: iload_2                   // 将第三个int类型本地变量加载到操作数栈顶
      30: invokevirtual #15         // 调用PrintStream的println方法
      33: getstatic     #14         // 获取System.out静态字段值
      36: aload_3                   // 将第四个引用类型本地变量加载到操作数栈顶
      37: invokevirtual #16         // 调用PrintStream的println方法
      40: return                    // 返回
}

这里的每一行是一条字节码指令,如aload_0是将第一个引用类型本地变量加载到操作数栈顶,putfield是将栈顶的数值存入到对象的字段中,iadd是将栈顶两个int类型数值相加并将结果压入栈顶,等等。

其实jvm的指令是可以分为9类。我们都分别了解一下。

1. 加载和存储指令

加载和存储指令是Java字节码中的一种类型,主要用于从本地变量表中加载值到操作数栈,或者将操作数栈的值存储到本地变量表中。这里的值可以是基本类型,也可以是引用类型。
以下是Java字节码中加载和存储指令的一部分:

指令描述
aload将引用类型本地变量加载到操作数栈顶
aload_0将第一个引用类型本地变量加载到操作数栈顶
aload_1将第二个引用类型本地变量加载到操作数栈顶
aload_2将第三个引用类型本地变量加载到操作数栈顶
aload_3将第四个引用类型本地变量加载到操作数栈顶
astore将栈顶引用类型数值存入本地变量
astore_0将栈顶引用类型数值存入第一个本地变量
astore_1将栈顶引用类型数值存入第二个本地变量
astore_2将栈顶引用类型数值存入第三个本地变量
astore_3将栈顶引用类型数值存入第四个本地变量
iload将int类型本地变量加载到操作数栈顶
iload_0将第一个int类型本地变量加载到操作数栈顶
iload_1将第二个int类型本地变量加载到操作数栈顶
iload_2将第三个int类型本地变量加载到操作数栈顶
iload_3将第四个int类型本地变量加载到操作数栈顶
istore将栈顶int类型数值存入本地变量
istore_0将栈顶int类型数值存入第一个本地变量
istore_1将栈顶int类型数值存入第二个本地变量
istore_2将栈顶int类型数值存入第三个本地变量
istore_3将栈顶int类型数值存入第四个本地变量

对于其他类型(如long,float,double),也有相应的lload,lstore,fload,fstore,dload,dstore等指令。

2. 算术指令

以下是Java字节码中的一些算术指令:

指令描述
iadd将栈顶两int类型数值相加并将结果压入栈顶
ladd将栈顶两long类型数值相加并将结果压入栈顶
fadd将栈顶两float类型数值相加并将结果压入栈顶
dadd将栈顶两double类型数值相加并将结果压入栈顶
isub将栈顶两int类型数值相减并将结果压入栈顶
lsub将栈顶两long类型数值相减并将结果压入栈顶
fsub将栈顶两float类型数值相减并将结果压入栈顶
dsub将栈顶两double类型数值相减并将结果压入栈顶
imul将栈顶两int类型数值相乘并将结果压入栈顶
lmul将栈顶两long类型数值相乘并将结果压入栈顶
fmul将栈顶两float类型数值相乘并将结果压入栈顶
dmul将栈顶两double类型数值相乘并将结果压入栈顶
idiv将栈顶两int类型数值相除并将结果压入栈顶
ldiv将栈顶两long类型数值相除并将结果压入栈顶
fdiv将栈顶两float类型数值相除并将结果压入栈顶
ddiv将栈顶两double类型数值相除并将结果压入栈顶

这些只是算术指令的一部分,还有很多其他的算术指令,如irem(取余),ineg(取负)等。
以下是Java字节码中各类别指令的一部分:

3. 类型转换指令

指令描述
i2l将栈顶int类型数值转换为long类型并压入栈顶
i2f将栈顶int类型数值转换为float类型并压入栈顶
i2d将栈顶int类型数值转换为double类型并压入栈顶

4. 对象和数组操作指令

指令描述
new创建一个对象,并将引用值压入栈顶
anewarray创建一个引用类型数组,并将引用值压入栈顶
arraylength获取数组的长度值,并将长度值压入栈顶

5. 操作数栈管理指令

指令描述
pop弹出栈顶数值
pop2弹出栈顶的一个或两个数值
dup复制栈顶数值并压入栈顶

6. 控制转移指令

指令描述
ifeq当栈顶int类型数值等于0时跳转
ifne当栈顶int类型数值不等于0时跳转
goto无条件跳转

7. 方法调用和返回指令

指令描述
invokevirtual调用实例方法
invokespecial调用构造函数,私有方法和父类方法
invokestatic调用静态方法
return从当前方法返回void

8. 异常处理指令

指令描述
athrow将栈顶的异常抛出

9. 同步指令

指令描述
monitorenter获取对象的锁
monitorexit释放对象的锁

学到了这些指令后,我们尝试的解析一下这个java类生成的字节码指令
下面是一个复杂的Java类(一个简单的计数器)及其对应的一部分字节码指令:

Java代码:

public class Counter {
    private int count = 0;

    public int getCount() {
        return count;
    }

    public void increment() {
        count++;
    }
}

对应的字节码指令(只展示了部分):

0: aload_0
1: dup
2: getfield      #2                  // Field count:I
5: iconst_1
6: iadd
7: putfield      #2                  // Field count:I
10: return

说明:

  • aload_0:将局部变量表的第0个局部变量加载到操作数栈顶,这里的局部变量0是this。
  • dup:复制栈顶的一个元素并将复制的元素重新压入栈顶。
  • getfield:从对象中取出一个字段的值,这里是取出count字段的值。
  • iconst_1:将int型常量1推送到操作数栈顶。
    - iadd:将栈顶的两个int型数值出栈,相加,然后将结果入栈。
  • putfield:将栈顶的一个值(即iadd的结果)存储到对象的字段中,这里是存储到count字段。
  • return:从当前方法返回。

总结一下指令命名规律

JVM字节码指令(opcode)的命名有其特点和规律,总结一些常见的命名技巧和规律:
通过命名规律,可以更容易地理解JVM字节码指令的含义和功能。

1. 操作数类型前缀

字节码指令通常以一个字符作为前缀,表示操作数的类型。这些字符包括:

  • i:表示操作数是int类型。
  • l:表示操作数是long类型。
  • f:表示操作数是float类型。
  • d:表示操作数是double类型。
  • a:表示操作数是对象引用类型。
  • b:表示操作数是byte类型。
  • c:表示操作数是char类型。
  • s:表示操作数是short类型。

例如:iload表示加载一个int类型的局部变量,fadd表示将两个float类型的值相加。

2. 指令的操作:

  • load:加载操作,通常表示从局部变量表或数组中加载一个值到操作数栈。
  • store:存储操作,通常表示将一个值从操作数栈存储到局部变量表或数组中。
  • addsubmuldivrem:分别表示加、减、乘、除和取余运算。
  • andorxor:分别表示位与、位或和位异或运算。
  • neg:表示取反操作。
  • shlshrushr:分别表示左移、有符号右移和无符号右移操作。
  • cmpeqcmpnecmpltcmpgecmpgtcmple:表示比较操作。

3. 指令的粒度:

  • const:用于表示将常量加载到操作数栈。
  • 23:表示指令操作两个或三个操作数。例如,iadd表示将栈顶两个int值相加,dup2表示复制栈顶两个元素。
  • x:表示指令可以操作多种类型的值。例如,aaload表示从数组中加载一个对象引用,bastore表示将一个byte或boolean值存储到数组中。

4. 数组操作相关:

  • aload:表示从数组中加载一个元素到操作数栈。
  • astore:表示将一个元素从操作数栈存储到数组中。
  • length:表示获取数组的长度。

5. 控制转移和方法调用:

  • goto:表示无条件跳转。
  • if:表示条件跳转。
  • return:表示从方法返回。
  • invoke:表示调用方法。

三、字节码指令的详细解读

jvm 字节码执行过程

1. JVM如何解析字节码指令

在这里插入图片描述

JVM(Java虚拟机)是一个基于栈的解释执行引擎,它通过逐个解析和执行字节码指令来运行Java程序。字节码指令是一种特定于JVM的低级二进制格式,它们将Java源代码的高级表示形式转换为一种更适合计算机执行的格式。在执行字节码之前,JVM首先将Java类加载到内存中,初始化类的静态变量,然后在需要时解析和执行相应的方法。以下是JVM解析字节码指令的过程:

  1. 类加载:JVM通过类加载器将.class文件加载到内存中,并进行链接和初始化。链接包括验证、准备和解析三个阶段。验证确保字节码符合JVM规范,准备阶段为类变量分配内存并设置初始值,解析阶段将符号引用转换为直接引用。初始化阶段为类变量赋予正确的初始值,并执行类构造器方法。

  2. 创建线程栈:每个Java线程在JVM中都有一个线程栈,用于存储方法调用所需的数据,包括局部变量、操作数栈和帧数据。线程栈中的每个方法调用都对应一个栈帧。

  3. 方法调用:当JVM调用一个方法时,会为该方法创建一个新的栈帧,并将其压入线程栈顶。栈帧包含了方法的局部变量表、操作数栈和帧数据。局部变量表用于存储方法的参数和局部变量,操作数栈用于存储计算过程中产生的中间结果,帧数据包含方法返回地址和其他一些元数据。

  4. 解析指令:JVM逐个解析方法中的字节码指令,根据指令操作码执行相应的操作。这些操作包括但不限于数据加载、存储、算术运算、类型转换、对象创建、方法调用、控制转移等。解析指令时,JVM会根据指令操作数类型和数量获取操作数(如果有),并根据指令语义对局部变量表和操作数栈进行操作。

  5. 执行指令:JVM根据指令语义执行相应的操作,这可能会导致更改局部变量表、操作数栈或其他系统状态。执行完指令后,JVM将程序计数器(PC)设置为下一条指令的地址,然后继续解析和执行下一条指令。

  6. 方法返回:当JVM遇到方法返回指令(如return)时,它会将当前栈帧弹出线程栈,并将返回值(如果有)压入调用者的操作数栈。然后,JVM将程序计数器设置为当前方法的返回地址,继续执行调用者的剩余指令。

通过以上过程,JVM能够逐个解析和执行字节码指令,从而完成Java程序的运行。

2. JVM如何执行字节码指令

在这里插入图片描述

Java虚拟机 它将字节码指令解析和执行过程分为以下几个步骤:

  1. 加载:首先,JVM将字节码文件(.class文件)加载到内存中。字节码文件是由Java编译器从Java源代码转换而来的。

  2. 验证:加载完成后,JVM会对字节码指令进行验证,确保它符合JVM的规范、安全且结构正确。

  3. 预处理:JVM可能会对字节码进行一些预处理操作,例如:解析、优化等。

  4. 解析与执行:经过预处理后,JVM会开始解析和执行字节码。JVM拥有一个程序计数器(PC寄存器),用以存储下一条需要执行的字节码指令的地址。JVM会根据程序计数器的指示,一条一条地解析并执行字节码指令。解析指令是指把二进制格式的指令转换成JVM可以理解的操作,执行指令则是指按照解析出来的操作进行相应的操作。

    例如,对于算术指令,JVM会从操作数栈取出操作数,执行相应的算术操作,并将结果压回操作数栈。对于方法调用指令,JVM会调度相应的方法,并为该方法创建一个新的栈帧。对于跳转指令,JVM会修改程序计数器的值,使其指向跳转目标的地址。

  5. 管理内存:在执行过程中,JVM还负责管理内存。根据字节码指令的需要,JVM会在堆中分配或回收对象的内存。

  6. 处理异常:如果在执行字节码指令过程中发生异常,JVM会寻找合适的异常处理器来处理它。

四、字节码工具的使用

1. 如何使用javap查看字节码

javap是Java官方提供的一个命令行工具,用于反编译已编译的Java类文件,输出字节码指令。要使用javap查看字节码,请按照以下步骤操作:

  1. 打开命令行窗口(在Windows上可以使用cmd,Linux和Mac上可以使用Terminal)。

  2. 定位到目标类文件所在的目录。例如,如果目标类文件在D:\MyJavaProject\out\production\MyJavaProject目录下,可以使用以下命令导航到该目录:

cd D:\MyJavaProject\out\production\MyJavaProject
  1. 使用javap命令查看字节码。以下是一些常用的选项:
  • -c:输出字节码指令。
  • -p:输出私有属性和方法。
  • -l:输出行号和局部变量表。
  • -v:输出详细信息。

例如,要查看名为MyClass的类的字节码,可以使用以下命令:

javap -c -p -l -v MyClass

这将输出MyClass类的字节码指令、私有属性、方法、行号和局部变量表等信息。

2. 如何使用ASM等工具修改字节码

ASM是一个Java字节码操作和分析框架,可以用来动态生成、修改和分析Java类文件。以下是一个使用ASM修改字节码的简单示例:

  1. 添加ASM依赖。在Maven项目中,可以在pom.xml文件中添加以下依赖:
<dependency>
    <groupId>org.ow2.asm</groupId>
    <artifactId>asm</artifactId>
    <version>9.1</version>
</dependency>
  1. 使用ASM Core API进行字节码修改。以下是一个简单的例子,使用ASM将一个类的方法返回值修改为42:
import org.objectweb.asm.*;

import java.io.FileInputStream;
import java.io.FileOutputStream;

public class AsmDemo {
    public static void main(String[] args) throws Exception {
        // 读取并修改字节码
        ClassReader classReader = new ClassReader(new FileInputStream("MyClass.class"));
        ClassWriter classWriter = new ClassWriter(classReader, 0);
        MyClassModifier classModifier = new MyClassModifier();
        classReader.accept(classModifier, 0);

        // 将修改后的字节码写入文件
        byte[] modifiedClassByteArray = classWriter.toByteArray();
        try (FileOutputStream fos = new FileOutputStream("MyClassModified.class")) {
            fos.write(modifiedClassByteArray);
        }
    }

    static class MyClassModifier extends ClassVisitor {
        public MyClassModifier() {
            super(Opcodes.ASM9);
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new MethodModifier(mv);
        }
    }

    static class MethodModifier extends MethodVisitor {
        public MethodModifier(MethodVisitor methodVisitor) {
            super(Opcodes.ASM9, methodVisitor);
        }

        @Override
        public void visitInsn(int opcode) {
            // 将方法的返回值修改为42(int类型)
            if (opcode == Opcodes.IRETURN) {
                super.visitLdcInsn(42);
            }
            super.visitInsn(opcode);
        }
    }
}

3. 如何使用JIT编译器优化字节码

JIT(Just-In-Time)编译器是Java虚拟机的一个组件,用于将字节码动态编译为本地机器代码,以提高程序执行速度。JIT编译器在运行时自动优化热点代码(即执行频繁的代码片段),无需手动启用或配置。

然而,可以使用一些JVM参数来控制JIT编译器的行为。以下是一些常用的JIT编译器选项:

  • -XX:+PrintCompilation:输出JIT编译过程中的日志信息。
  • -XX:CompileThreshold:设置触发JIT编译的方法调用次数阈值(默认值为10000)。
  • -XX:ReservedCodeCacheSize:设置JIT编译器的代码缓存大小,可以调整此值以改善性能。

例如,要启用JIT编译器日志,并将编译阈值设置为5000,可以在启动Java程序时添加以下JVM参数:

java -XX:+PrintCompilation -XX:CompileThreshold=5000 MyClass

JIT编译器的优化是透明的,通常无需手动干预。在实际应用中,可能需要根据具体场景和性能需求调整JVM参数。

五、字节码指令的优化策略

1. 常量折叠优化:

常量折叠是一种编译器的优化技术,旨在计算和简化在编译时期已知的常量表达式。例如,对于表达式2+3,编译器在编译时就可以直接将其计算为5,而不必等到运行时。

2. 代码重排序优化:

代码重排序是一种优化策略,它通过调整指令的执行顺序,来提高指令的并行度以提高程序运行效率。但是需要注意的是,代码重排序不能改变程序的语义,也就是说,重排序后的代码执行结果必须与原始代码一致。

3. 常量传播优化:

常量传播是一种编译器优化技术,通过跟踪程序中的常量赋值语句,将这些常量传播到他们可能被使用的地方。这样可以避免运行时的查找或计算常量值的操作,提高程序运行效率。

4. 公共子表达式消除:

公共子表达式消除是一种编译器优化技术,旨在找出并消除在多个位置重复出现的表达式。通过避免重复计算相同的表达式,可以显著提高程序的运行效率。

5. 死代码消除:

死代码是指在程序运行过程中永远不会被执行到的代码。死代码消除就是找出并删除这些代码,从而减少程序的大小,并稍微提高程序的运行效率。

六、字节码指令的实际应用

  1. AOP编程
  2. 动态代理
  3. ORM框架
  4. 热部署

7.参考文档

  1. https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
  2. https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
  3. https://blog.jamesdbloom.com/JVMInternals.html

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

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

相关文章

在emacs中,设置latex的主文档

文档&#xff1a; chapter1.tex chapter2.tex main.tex 在chapter1.tex中&#xff0c;先按下 ctrlc ctrln&#xff0c;再按下ctrlc ctrla&#xff0c;在下方的提示框中输入主文档。

链路层3:VLAN的配置与分析

VLAN的帧格式 VLAN数据帧的传输 在以太网中&#xff0c;加了标签tag的VLAN数据帧我们叫做V-MAC帧&#xff0c;普通的数据帧我们叫做MAC帧。对于主机来说&#xff0c;它只认识普通的MAC帧&#xff1b;对于主机&#xff0c;V-MAC帧和MAC帧它都认。所以&#xff0c;实际上的V-MAC…

docker中使用GPU+rocksdb

配置环境 delldell-Precision-3630-Tower  ~  lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 20.04.6 LTS Release: 20.04 Codename: focaldelldell-Precision-3630-Tower  ~  nvcc --version nvcc: NVIDIA (R) Cuda comp…

如果C盘满了怎么办

相信这个问题是困扰了很多人的。 1、清理 1.1清理缓存 这种适合一些小白&#xff0c;清理C盘中的缓存&#xff0c;但是治标不治本。上正文 &#xff08;1&#xff09;打开电脑&#xff0c;输入winr &#xff08;2&#xff09;输入%temp% 该文件目录下全是缓存文件可以删除&…

2023年京东双11红包领取入口口令活动时间是从什么时候开始到几月几号结束如何领取2023京东双十一红包优惠券?

2023年京东双11红包领取活动时间是什么时候&#xff1f; 京东双11红包领取活动时间将于2023年10月23日00:00开始至11月11日23:59结束&#xff1b; 2023年京东双11红包领取入口在哪里如何天天免费领取&#xff1f; 2023年京东双11红包口令「红包到手677」&#xff0c;请在活动…

完美解决lftp遇到put: Access failed: 553 Could not create file.

目录 一、问题 二、原因 三、解决方法 一、问题 put: Access failed: 553 Could not create file. 二、原因 &#xff08;1&#xff09;没有关闭SeLinux &#xff08;2&#xff09;linux默认安装vsftp服务之后只允许匿名用户的访问和下载&#xff0c;不支持上传。 三、解决方…

【Java】nextInt()后面紧接nextLine()读取不到数据/InputMismatchException异常的解决方案

错误如下&#xff1a; 有时候还会抛出InputMismatchException异常 看&#xff01;我只输入了一个5&#xff0c;并没有给str赋值&#xff0c;它就已经将结果打印出来了&#xff01;这就意味着&#xff0c;str是读取到了数据的&#xff0c;只不过这个数据并不是我们想要的输入的…

unity ugui text 超链接和下划线,支持部分富文本格式

unity版本&#xff1a;2021.3.6f1 局限性&#xff1a; 1.测试发现不能使用 size 富文本标签, 2.同一文本不能设置不同颜色的超链接文本 其它&#xff1a;代码中注释掉使用innerTextColor的地方&#xff0c;可以使用富文本设置超链接颜色&#xff0c; 但是下划线是文本本身颜色 …

Mybatis学习笔记注解/xml映射/动态SQL%%%Mybatis教程

介绍 Mybatis 是一款优秀的持久层框架&#xff0c;用于简化 JDBC 的开发 MyBatis中文网 Mybatis 入门 快速入门 步骤 创建 SpringBoot 工程、数据库表 user、实体类 User引入 Mybatis 相关依赖&#xff0c;配置 Mybatis&#xff08;数据库连接信息&#xff09;编写 SQL 语…

adb调试Linux嵌入式设备记录

1. ADB的全称为Android Debug Bridge&#xff0c;调试设备或调试开发的Android APP。 2.adb的windows下载安装路径&#xff1a;SDK 平台工具版本说明 | Android 开发者 | Android Developers 3.linux中安装adb,参考该链接&#xff1a; https://www.cnblogs.com/androidsu…

Springboot+vue的财务管理系统(有报告),Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的财务管理系统&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的财务管理系统&#xff0c;采用M&#xff08;model…

基于Seata的分布式事务方案

在Seata中&#xff0c;有4种分布式事务实现方案 XA、AT、TCC、Saga 其中XA利用了数据库的分布式事务特性&#xff0c;AT相当于框架去控制事务回滚。TCC手写三个方法&#xff0c;saga手写两个方法。 AT的性能和编写比较折中&#xff0c;是最常用的一种。TCC一些视频教程中介绍…

windows系统安装openssl并且转换证书格式

概述 碎碎念&#xff0c;如果你有MAC电脑&#xff0c;就别折腾了&#xff0c;直接用MAC电脑吧,不用安装直接用openssl 本文主要讲到了openssl的基本使用方法&#xff0c;开发环境为windows&#xff0c;开发工具为VS2019.本文主要是说明openssl如何使用&#xff0c;不介绍任何理…

判断某点是否在三角形内(Python)

已知三角形的三个顶点坐标&#xff0c;判断某个点是否在三角形中&#xff08;在三角形的边上&#xff0c;我们也视作在三角形中&#xff09;&#xff0c;我们提供不同的方法。 方法1&#xff1a;内角和等于360 方法2&#xff1a;等面积法 即对于△ABC内的某一点P&#xff0c;…

LInux文件权限相关知识介绍

LInux文件权限相关知识分享&#x1f60e; 前言&#x1f64c;Linux相关权限的概念&#xff1a;文件类型基本权限文件访问权限的相关设置方法chmod① 用户表示符/-权限字符②三位8进制数字 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢…

java+springboot+vue高校毕业生就业统计管理系统022xr

系统的设计与实现采用Spring、SpringMVC和MyBatis作为主体框架,系统设计遵循界面层、业务逻辑层和数据访问层的Web开发三层架构。采用B/S结构,使得系统更加容易维护。系统的设计与实现主要实现角色有管理员和用户,管理员在后台管理用户表模块、token表模块、生源信息模块、招聘…

代码随想录Day19 LeetCode T669修剪二叉搜索树 LeetCode T108将有序数组转化为二叉搜索树 T538 把二叉搜索树转化为累加树

LeetCode T669 修剪二叉搜索树 题目链接:669. 修剪二叉搜索树 - 力扣&#xff08;LeetCode&#xff09; 题目思路 这题我们有几个思路需要避坑,首先我们不能这样想,比如遇见比low值还小的节点值,不能直接返回null,而是考虑该节点的右子树有没有符合题目需求的节点值存在,同理删…

Java学习之TreeSet

Java TreeSet 底层是红黑树实现 将元素插入TreeSet add() - 将指定的元素插入集合 addAll() - 将指定集合的所有元素插入集合 import java.util.TreeSet;class Main {public static void main(String[] args) {TreeSet<Integer> evenNumbers new TreeSet<>();/…

二十三、【五种图层】

文章目录 像素图层智能图层文字图层形状图层调整图层 像素图层 像素图层由空白像素图层组成&#xff0c;上边没有任何颜色&#xff0c;如下图&#xff0c;我们可以使用画笔工具在空白相处图层上进行修改&#xff1a; 智能图层 智能图层可以记录该图层当前的数据在被编辑后可…

ShareX使用说明——优秀的录屏软件

ShareX初识 ShareX 是一个自由及开放源代码的截图录像软件&#xff0c;目前仅支持Windows系统。 项目源代码在GitHub平台上发布&#xff0c; 软件可以在Microsoft商店和Steam上下载。 ShareX is a free and open source program that lets you capture or record any area of y…