JVM 虚拟机栈

news2024/10/5 7:04:37

虚拟机栈概述

背景:

  • 由于跨平台性的设计,Java 的指令都是根据栈来设计的。不同平台 CPU 架构不同,所以不能设计为基于寄存器的
  • 优点是跨平台, 指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令

内存中的栈和堆:

  • 栈是运行时的单位,即程序如何执行,而堆是存储的单位,数据怎么放,放在哪儿。

Java 虚拟机栈:

  • Java 虚拟机栈(Java Virtual Machine Stack), 早期也叫 Java 栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(Stack Frame),对应着一次次的 Java 方法调用
  • 是线程私有的
  • 生命周期和线程一致
  • 主管 Java 程序运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果、并参与方法的调用和返回。

栈的特点:

  • 栈是一种快速高效的分配存储方法,访问速度仅次于程序计数器
  • JVM 直接对 Java 栈的操作只有两个: 每个方法执行,伴随着进栈(入栈、压栈)。执行结束后的出栈工作。
  • 对于栈来说不存在垃圾回收问题 

栈的存储单位

栈中的存储:

  • 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在
  • 在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)
  • 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息

栈运行原理:

public class StackFrameTest {
    public static void main(String[] args) {
        StackFrameTest stackFrameTest = new StackFrameTest();
        stackFrameTest.method1();
    }
    public void method1(){
        System.out.println("method1() 开始执行");
        method2();
        System.out.println("method1() 执行结束");
    }

    public int method2(){
        System.out.println("method2() 开始执行");
        int i = 10;
        int m = (int) method3();
        System.out.println("method2() 即将结束");
        return i + m;
    }

    public double method3(){
        System.out.println("method3() 开始执行");
        double j = 20.0;
        System.out.println("method3() 即将结束");
        return j;
    }
}

  • JVM 直接对 Java 栈的操作只有两个,这就是对栈的压栈和出栈,遵循“先进后出” 原则。
  • 在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame), 与当前栈帧对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)
  • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作
  • 如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,称为新的当前帧
  • 不同的线程中所包含的栈帧是不允许相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧
  • 如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新称为当前栈帧
  • Java 方法有两种返回函数的方式,一种是正常函数返回,使用 return 指令,另外一种是抛出异常

栈帧的内部结构:

  • 局部变量表 (Local Variables)
  • 操作数栈 / 表达式栈 (Operand Stack) 
  • 动态链接 / 指向运行时常量池的方法引用( Dynamic Linking)
  • 方法返回地址 / 方法正常退出或异常退出的定义 (Return Address)
  • 一些附加信息

局部变量表(Local Variables)

  • 局部变量表也称之为局部变量数据或本地变量表
  • 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference)、以及 returnAddress 类型
  • 由于局部变量表是建立在线程的栈上,是线程私有树,因此不存在数据安全问题
  • 局部变量表所需的容量大小是编译期确定下来的,并保存在方法的 Code 属性的 maximum local variables 数据项中。在方法运行期间是不会改变局部变量表的大小的
  • 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求,进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少
  • 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的产地过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁

Slot:

  • 参数值的存放总是在局部变量数组的 index0 开始,到数组长度 -1 的索引结束
  • 局部变量表,嘴基本的存储单元是 Slot (变量槽)
  • 局部变量表中存放编译器可知的各种数据类型(8种),引用类型 (reference), returnAddress 类型的变量
  • 在局部变量表里,32位以内的数据只占用一个 slot(包括 returnAddress 类型),64位的类型(long 和 double) 占用两个 slot。 byte、short、char、boolean 在存储前被转换为 int
  • JVM 会为局部变量表种的每一个 Slot 都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值
  • 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按顺序被复制到局部变量表中的每一个Slot上
  • 如果需要访问局部变量表中一个 64 bit de 局部变量值时,只需要使用前一个索引即可
  • 如果当前帧是由构造方法或者实例方法创建的, 那么该对象引用 this 将会存放在 index 为0的 slot 处,其余的按照参数表顺序继续排列
  • 栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后声明的新的局部变量就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的

静态变量和局部变量的对比:

  • 参数表分配完毕之后,再根据方法体内定义的变量的顺序和作用域分配
  • 类变量表有两次初始化的机会,第一次是在 " 准备阶段 ",执行系统初始化, 对类变量设置零值,另一次则是在 "初始化" 阶段,赋予程序员在代码中定义的初始值
  • 和类变量初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用

操作数栈(Operand Stack):

  • 在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push) / 出栈(pop)
  • 某些字节码指令将压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再压入栈。
  • 比如: 执行复制、交换、求和等操作
  • 主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
  • 操作数栈就是 JVM 执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的
  • 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需要的最大深度在编译期就定义好了,保存在方法的 Code 属性中,为 max_stack 的值
  • 栈中的任何一个元素都是可以任意的 Java 数据类型,32b 的类型占用一个栈单位深度, 64bit 的类型占用两个栈单位深度
  • 操作数并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push) 和 出(pop) 操作完成一次数据访问
  • 如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新 PC 寄存器中下一条需要执行的字节码指令
  • 操作数栈中元素的数据类型必须与字节码指令序列严格匹配,这由编译器在编译期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证
  • 另外,我们说 Java 虚拟机的解释引擎是基于栈的操作引擎,其中的栈指的就是操作数栈

public class testAddOperation {
    public static void main(String[] args) {
        byte i = 15;
        int j = 8;
        int k = i + j;
    }
} 

// javap -verbose .\testAddOperation.class
public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: bipush        15
         2: istore_1
         3: bipush        8
         5: istore_2
         6: iload_1
         7: iload_2
         8: iadd
         9: istore_3
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  args   [Ljava/lang/String;
            3       8     1     i   B
            6       5     2     j   I
           10       1     3     k   I

代码追踪

步骤1-4:

 步骤5-8:

栈顶缓存技术

  • 基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必须需要使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派(instruction dispatch) 次数和内存 读 / 写次数
  • 栈顶缓存(ToS,Top - of - Stack Cashing) 技术,将栈顶元素全部缓存在物理 CPU 的寄存器中,以此降低对内存的读 / 写次数,提升执行引擎的执行效率

动态链接(Dynamic Linking)

  • 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属的方法引用。包含这个引用的目的就是为了支持当前方法的代码实现动态链接(Dynamic Linking)。比如: invokedynamic 指令
  • 在 Java 源文件被编译到字节码文件中时,所有的变量和方法引用都做为符号引用(Symbolic Reference) 保存在 class 文件的常量池里。比如: 描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
public class DynamicLinkingTest {
    int num = 10;
    public void methodA(){
        System.out.println("methodA()...");
    }

    public void methodB(){
        System.out.println("methodB()...");
        methodA();
        num++;
    }
}

方法的调用:解析和分派

  • 在JVM 中,将符号引用转换为调用方法来直接引用与方法的绑定机制相关
  • 由于一些编程语言保持着支持封装、继承和多态的面向对象特性,那么自然也就具备了早期绑定和晚期绑定方式
  • Java 中任何一个普通的方法都具备虚函数的特征,如果在java 程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字 final 来标记这个方法

静态链接:

当一个字节码文件被装载进 JVM 内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接

动态链接:

如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换具备动态性,因此也就称之为动态链接

对应的方法绑定机制为:  早期绑定(Early Binding) 和晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次

早期绑定:

指被调用的目标方法如果在编译期可知,且运行期保持不变时,即可将这个方法与所属的类型进行绑定,这样依赖,由于明确了被调用的目标究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用

晚期绑定:

如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定

非虚方法:

  • 如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。
  • 静态方法、私有方法、final 方法、实例构造器、父类方法都是非虚方法
  • 其他方法称为虚方法

调用指令:

调用类型指令说明
普通调用指令invokestatic调用静态方法,解析阶段确定唯一方法版本
invokespecial调用<init>方法, 私有及父类方法,解析阶段确定唯一方法版本呢
invokevirtual调用所有虚方法
invokeinterface调用接口方法
动态调用指令invokedynamic动态解析出需要调用的方法,然后执行
class Father{
    public Father(){
        System.out.println("father的构造器");
    }

    public static void showStatic(String str){
        System.out.println("father " + str);
    }

    public final void showFinal(){
        System.out.println("father show final");
    }

    public void showCommon(){
        System.out.println("father 普通方法");
    }
}

interface Func{
    public boolean func(String str);
}
public class Son extends Father{
    public Son(){
        //  invokespecial
        super();
    }

    public Son(int age){
        //  invokespecial
        this();
    }

    // 静态方法不能被重写
    public static void showStatic(String str){
        System.out.println("son " + str);
    }

    private  void showPrivate(String str){
        System.out.println("son private " + str);
    }

    public void show(){
        // invokestatic
        showStatic("hello");
        // invokestatic
        super.showStatic("good!");
        // invokespecial
        showPrivate("world");
        // invokespecial
        super.showCommon();
        // invokevirtual,当显示的增加 super. 会变成invokespecial
        // 由于此方法声明有 final,不能被子程序重写,因此为非虚方法
        showFinal();
        // invokevirtual
        showCommon();
        info();
        MethodInterface in = null;
        //  invokeinterface
        in.methodA();
    }

    public void info(){

    }
    public void lambda(Func func){
        return;
    }

    public static void main(String[] args) {
        Son son = new Son();
        son.show();

        Func func = s-> {
            return true;
        };
        son.lambda(func);

        son.lambda(s->{
            return true;
        });
    }
}

动态类型语言和静态类型语言:

  • 静态类型语言是判断变量自身的类型信息
  • 动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有

Java 语言中方法重写的本质:

  • 找到 操作数栈顶的第一个元素所执行的对象的实际类型, 记作 C
  • 如果在类型 C 中找到与常量中的描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用,查找过程结束,如果不通过,则返回 Java.lang.IllegalAccessError 异常
  • 否则,按照继承关系从下往上依次对 C 的各个父类进行第 2 步的搜索和验证过程
  • 如果始终没有找到合适的方法,则抛出 java.lang.AbstractMethodEroor 异常

IllegalAccessError 介绍:

程序视图访问或修改一个属性或调用一个方法,这个属性或方法,你没有权限访问。一般的,这个会导致编译期异常。这个错误如果发生在运动时,就说明一个类发生了不兼容的改变

虚方法表:

  • 在面向对象的编程中,会频繁的使用动态分派,如果在每次动态分派的过程中都要重新在类的方法元数据中搜索合适的目标的话就可能影响到执行效率。因此,为了提高性能,JVM 采用在类的方法区建立一个虚方法表(virtual method table, 非虚方法不会出现在表中) 来实现。使用索引表来代替查找
  • 每个类都有一个虚放发表,表中存放着各个方法的实际入口
  • 虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后,JVM 会把该类的放发表也初始化完毕 

方法返回地址

  • 存放调用该方法的 PC 寄存器的值
  • 一个方法的结束,有两种方式: 正常执行完成,出现未处理的异常,非正常退出
  • 无论通过哪种方法退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的 pc 计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来去欸的那个,栈帧中一般不会保存这部分信息
  • 在字节码指令中,返回指令包含 ireturn (当返回值是 boolean、byte、char、short 和 int 类型时使用)、lreturn、freturn、dreturn 以及 areturn,另外还有一个 return 指令供声明为 void 的方法、实例初始化方法、类和接口的初始化方法使用

一些附加信息

栈帧中还允许携带与 Java 虚拟机实现相关的一些附加信息,例如,对程序调试提供支持的信息

栈的相关面试题

栈种可能出现的问题:

Java 虚拟机规范允许 Java 栈的大小是动态的或者固定不变的,可使用 -Xss 来设置线程最大栈空间。

public class StackErrorTest {
    // 栈种异常
    private static int count = 1;
    public static void main(String[] args) {
        // 可以在设置 -Xss256k后 再运行查看 count 的最大值
        System.out.println(count);
        count++;
        main(args);
    }
}
  • 如果采用固定大小的 Java 虚拟机栈,那每一个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出一个 StackOverflowError 异常
  • 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutofMemoryError 异常

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

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

相关文章

CVE-2023-21839 Weblogic RCE

前言 刷B站的时候给我推的一个WebLogic的比较新的漏洞&#xff0c;可以通过此漏洞直接达到RCE进行getShell的效果&#xff0c;于是就简单复现和分析一下&#xff0c;做个记录。 视频链接 漏洞简单分析 此漏洞是属于WebLogic的JNDI注入漏洞&#xff0c;漏洞造成的原因是Weblo…

《创新者的基因》读书笔记

本书是企业创新管理的研究成果&#xff0c;针对个人和企业如何培养、提升商业创新能力给出了行动指南&#xff0c;每一种能力都提供了很多训练小技巧&#xff0c;在此不一一列举&#xff0c;只写自己的读书笔记、对策和思考。 破坏性创新者的基因 发问&#xff1a;目的是提出…

Java字符串详解:概念、特点与常见的使用场景

Java字符串是开发中经常使用到的一种数据类型&#xff0c;使用它可以处理文本、URL、文件路径等多种类型的数据。本文将对Java字符串的概念、特点以及常见使用场景进行详细解释。本文将分为以下几个部分&#xff1a; String的概念和特点Java字符串常量池字符串的不可变性使用e…

LeetCode - 3. 无重复字符的最长子串

写在前面&#xff1a; 题目链接&#xff1a;LeetCode - 3. 无重复字符的最长子串 题目难度&#xff1a;中等 编程语言&#xff1a;C 一、题目描述 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释…

【RabbitMQ】SpringAMQP

RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式&#xff1a; 同步通讯&#xff1a;就像打电话&#xff0c;需要实时响应。 异步通讯&#xff1a;就像发邮件&#xff0c;不需要马上回复。 两种方式各有优劣&#xff0c;打电话可以立即得到响应&am…

快速了解C语言的基本元素

C语言是一种编程语言&#xff0c;和其它语言一样&#xff0c;也定义了自己的语法和词汇。学习C语言&#xff0c;首先要学习C语言的词汇&#xff0c;再学习C语言的语法规则&#xff0c;然后由词汇构成语句&#xff0c;由语句构成源程序&#xff0c;源程序也称为源代码或代码&…

ORB-SLAM2的稠密重建实战(1) — 流程与函数功能说明

目录 0 系统整体流程 1 输出信息 2 初始化文件 3 初始化并运行追踪线程Tracking Step1&#xff1a;地图初始化 Step2&#xff1a;初始化成功&#xff08;mbOnlyTracking&#xff09; Step3&#xff1a;局部地图跟踪TrackLocalMap() Step4&#xff1a;跟踪成功 Step5&a…

【EKF】卡尔曼滤波的二维应用实例

前言 在上期&#xff0c;使用一个简单的一维应用实例来加深了卡尔曼滤波的印象后&#xff0c;使用一个二维的例子来看一下卡尔曼的效果。使用一个自由落体的例子来说明&#xff0c;假设一个物体在重力作用下&#xff0c;速度由0开始做自由落体运动&#xff0c;有观测装置对该物…

自动化部署编译部署【.net core】

自动化部署编译部署【.net core】 github 自动化编译部署 .NET 程序&#xff0c;程序有两个服务&#xff0c;一个是api&#xff0c;一个是admin. 需要部署到两台机器上(测试和正式)&#xff0c;所以采用两个Action来处理 项目目录结构 root ├── Config │ ├── deploy …

【Linux】进程间通信 —— 共享内存

文章目录 &#x1f4d5; 共享内存的原理&#x1f4d5; 代码实现 & 深入理解共享内存shmget() 函数shmctl() 、shmdt()、shmat()特点 &#x1f4d5; 源代码comm.hppserver.ccclient.cc &#x1f4d5; 共享内存的原理 我们知道&#xff0c;如果想实现进程间通信&#xff0c;…

Linux Shell 实现一键部署subversion

subversion SVN是subversion的缩写&#xff0c;是一个开放源代码的版本控制系统&#xff0c;通过采用分支管理系统的高效管理&#xff0c;简而言之就是用于多个人共同开发同一个项目&#xff0c;实现共享资源&#xff0c;实现最终集中式的管理。 TortoiseSVN TortoiseSVN 是…

C语言函数大全-- t 开头的函数

C语言函数大全 本篇介绍C语言函数大全-- t 开头的函数 1. tan&#xff0c;tanf&#xff0c;tanl 1.1 函数说明 函数声明函数功能double tan(double x)计算 以弧度 x 为单位的角度的正切值&#xff08;double&#xff09;float tanf(float x)计算 以弧度 x 为单位的角度的正…

Spring Boot项目创建和使用

一、Spring Boot简介 1.概念 Spring Boot 就是 Spring 框架的脚⼿架&#xff0c;它就是为了快速开发 Spring 框架⽽诞⽣的。 2.优点 有快速集成框架&#xff0c;可以快速添加外部jar包内置web框架&#xff0c;可以直接运行可以快速部署&#xff0c;不依赖任何外部的web容器…

【牛客刷题专栏】0x26:JZ25 合并两个排序的链表(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

MATLAB 之 数值数据,矩阵的表示和变量及其操作

文章目录 一、数值数据1. 数值数据类型的分类1.1 整型1.2 浮点型1.3 复型 2. 数据的输出格式 二、矩阵的表示1. 矩阵的建立1.1 直接输入法建立矩阵1.2 已建好的矩阵建立更大的矩阵 2. 冒号表达式3. 矩阵元素的引用3.1 矩阵元素的引用方式3.2 利用冒号表达式获得子矩阵3.3 利用空…

Spring执行流程Bean生命周期

Spring执行流程 说明&#xff1a;这里只是说的大概流程&#xff0c;不是严格按照源码上一步一步说的。 简单来说&#xff0c;分为下边四个步骤&#xff1a; 启动Spring容器实例化Bean&#xff08;分配内存空间&#xff09;将Bean注册到Spring当中&#xff08;存操作&#xf…

【常用 Linux 命令的基本使用】总结篇(附必要的 Ubuntu 截图)

本文目录 1. 常用 Linux 命令的基本使用1.1 学习 Linux 终端命令的原因1.2 常用 Linux 命令的基本使用1.3 自动补全 2. Linux 终端命令格式2.1 终端命令格式2.2 查阅命令帮助信息&#xff08;了解&#xff09; 3. 文件和目录常用命令3.1 查看目录内容3.1.1 终端实用技巧3.1.2 l…

shell数组

目录 一&#xff1a;数组定义方法 1、方法一 ​ 2、方法二 ​3、方法三 ​4、方法四 5、判断数组是否完整 &#xff08;1&#xff09;方法一 &#xff08;2&#xff09;方法二&#xff1a;通过脚本 二&#xff1a;获取数组值 1、获取数组长度 2、获取数组数据列表 3、…

本地部署 Stable Diffusion web UI

本地部署 ChatGLM-6B 0. 什么是 Stable Diffusion1. 什么是 Stable Diffusion web UI2. Github 地址3. 安装 Miniconda34. 创建虚拟环境5. 安装 Stable Diffusion web UI6. 启动 Stable Diffusion web UI7. 访问 Stable Diffusion web UI8. 其他 0. 什么是 Stable Diffusion S…

UE4 面试题整理

1、new与malloc的区别 new&#xff1a; new首先会去调用operator new函数&#xff0c;申请足够的内存&#xff08;大多数底层用malloc实现&#xff09;&#xff0c;然后调用类型的构造函数来初始化变量&#xff0c;最后返回自定义类型的指针&#xff0c;delete先调用析构函数&…