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

JVM 四虚拟机栈

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

内存中的栈与堆
有不少Java开发人员一提到Java内存结构,就会非常粗粒度地将JVM中的内存区理解为仅有Java堆(heap)和Java栈(stack),他们的关系是,栈是运行时的单位,而堆是存储的单位
● 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据。
● 堆解决的是数据存储的问题,即数据怎么放,放哪里
在这里插入图片描述
Java虚拟机栈是什么?
Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用,是线程私有的。它的生命周期和线程一致(上一章已经说过的了,栈是线程独有的)

作用
主管Java程序的运行,它保存方法的局部变量、部分结果,并参与方法的调用和返回。

栈的特点

栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。

JVM直接对Java栈的操作只有两个:
● 每个方法执行,伴随着进栈(入栈、压栈)
● 执行结束后的出栈工作

对于栈来说不存在垃圾回收问题(栈存在溢出的情况)

在这里插入图片描述

栈中可能出现的异常
Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的
● 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError 异常。
● 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出一个 OutOfMemoryError 异常。

案例(我们仅演示第一点,因为第二个问题需要在我们虚拟机内存不够的时候才会报)
在这里插入图片描述

/*** 测试结果,main递归执行11417次栈溢出** 设置栈的大小为:-Xss256k** 修改后只递归2000次便溢出,修改生效***/
public class StackTest {public static int count;public static void main(String[] args) {System.out.println(count);count++;main(args);}
}

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

JVM直接对Java栈的操作只有两个,就是对栈帧的压栈和出栈,遵循“先进后出”/“后进先出”原则。

在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧相对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。

执行引擎运行的所有字节码指令只针对当前栈帧进行操作。

如果在该方法中调用了其他方法,对应的新的栈帧会被创建出来,放在栈的顶端,成为新的当前帧。
在这里插入图片描述
不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧。

如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。

Java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令;另外一种是抛出异常。不管使用哪种方式,都会导致栈帧被弹出。

/*** 测试手段:三个方法嵌套运行debug* 打印如下:* main方法开始执行* method1 begin* method2 begin* method3 begin* method3 end* method2 will end(wait return)* method1 end* main方法正常结束*/
public class StackWorkTest {public static void main(String[] args) throws Exception{System.out.println("main方法开始执行");StackWorkTest stackWorkTest = new StackWorkTest();try {stackWorkTest.method01();}catch (Exception e) {e.printStackTrace();}System.out.println("main方法正常结束");}//整体是嵌套关系public void method01(){System.out.println("method1 begin");//执行到这里时method1是当前栈帧method02();System.out.println("method1 end");//执行到这里(回到method1时)method1是当前栈帧
//        int i = 10 / 0;return; //这里写和不写都是一样的,void方法最终都会有默认的return;当然我们可以通过return;使得方法提前结束}public int method02(){System.out.println("method2 begin");int i = 10;int v = (int) method03();System.out.println("method2 will end(wait return)");return v;}public double method03(){System.out.println("method3 begin");double i = 20.0;System.out.println("method3 end");return i;}
}

结束方式:return or exception without try-catch,上面是方法正常结束的例子

package com.sobot.net.jvm;/*** 异常时打印如下** main方法开始执行* method1 begin* method2 begin* method3 begin* method3 end* method2 will end(wait return)* method1 end* Exception in thread "main" java.lang.ArithmeticException: / by zero* 	at com.sobot.net.jvm.StackWorkTest.method01(StackWorkTest.java:39)* 	at com.sobot.net.jvm.StackWorkTest.main(StackWorkTest.java:30)*/
public class StackWorkTest {public static void main(String[] args) throws Exception{System.out.println("main方法开始执行");StackWorkTest stackWorkTest = new StackWorkTest();
//        try {
//            stackWorkTest.method01();
//        }catch (Exception e) {
//            e.printStackTrace();
//        }stackWorkTest.method01();System.out.println("main方法正常结束");}//整体是嵌套关系public void method01(){System.out.println("method1 begin");//执行到这里时method1是当前栈帧method02();System.out.println("method1 end");//执行到这里(回到method1时)method1是当前栈帧int i = 10 / 0;return;}public int method02(){System.out.println("method2 begin");int i = 10;int v = (int) method03();System.out.println("method2 will end(wait return)");return v;}public double method03(){System.out.println("method3 begin");double i = 20.0;System.out.println("method3 end");return i;}
}

栈帧的内部结构

每个栈帧中存储着:

● 局部变量表(Local Variables)
● 操作数栈(operand Stack)(或表达式栈)
● 动态链接(DynamicLinking)(或指向运行时常量池的方法引用)
● 方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
● 一些附加信息
在这里插入图片描述
并行每个线程下的栈都是私有的,因此每个线程都有自己各自的栈,并且每个栈里面都有很多栈帧,栈帧的大小主要由局部变量表 和 操作数栈决定的,而栈中栈帧的数目又受到栈帧大小的影响
在这里插入图片描述
局部变量表(Local Variables)
● 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型、对象引用(reference),以及returnAddress类型。
● 由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
● 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
● 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。 参考上一个案例,默认main方法可以嵌套执行11400次,但我们改了栈的大小为256k时仅能嵌套2000多次
● 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。

案例

package com.sobot.net.jvm;import java.util.Date;//局部变量表的大小在编译时就已经确定下来了
//普通方法入栈后栈中局部变量表都默认会有this,静态方法局部变量表没有this
public class LocalVariableTest {public LocalVariableTest() {}public LocalVariableTest(int count) {this.count = count;}private int count = 0;public static void main(String[] args) {LocalVariableTest localVariableTest = new LocalVariableTest();int num = 10;localVariableTest.test();}public void test(){Date date = new Date();int b = 10;double a = 10.0;String name = "atgwqqqqqqw";String weight = "asau";//如果不声明test变量,即这行代码变为test(date, name);时,局部变量表就没有test这个变量了String test = test(date, name);System.out.println(date + name);}public static void staticTest(){LocalVariableTest localVariableTest = new LocalVariableTest();Date date = new Date();int count = 10;System.out.println(count);//不能使用this,因为this变量不在当前方法的局部变量表里
//        System.out.println(this.count);}public String test(Date date,String str) {return date + str;}public void test3(Date date,String str) {this.count++;}public void test4() {int a = 0;{int b = 0;b = a + 1;}int c = a + 1;}
}

main方法中的局部变量如下

在这里插入图片描述
起始pc指局部变量从什么时候生效的,数字代表的是字节码的行号,参考字节码如下
在这里插入图片描述
首先args参数从最开始,也就是字节码第0行就开始生效了,而localVariableTest在字节码第8行生效,num参数在第11行生效,长度即代表生效范围
在这里插入图片描述
计算公式为字节码长度减去起始PC

关于Slot的理解
● 局部变量表,最基本的存储单元是Slot(变量槽)
● 参数值的存放总是在局部变量数组的index0开始,到数组长度-1的索引结束。
● 局部变量表中存放编译期可知的各种基本数据类型(8种),引用类型(reference),returnAddress类型的变量。
● 在局部变量表里,32位以内的类型只占用一个slot(包括returnAddress类型),64位的类型(long和double)占用两个slot。
● byte、short、char 在存储前被转换为int,boolean也被转换为int,0表示false,非0表示true。
● JVM会为局部变量表中的每一个Slot都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值
● 当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个slot上
● 如果需要访问局部变量表中一个64bit的局部变量值时,只需要使用前一个索引即可。(比如:访问long或doub1e类型变量)
● 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用this将会存放在index为0的slot处,其余的参数按照参数表顺序继续排列。
在这里插入图片描述
关于占用曹的大小,我们以上个案例中的test方法演示,如下
在这里插入图片描述
int型占用一个槽(43-42),同理String类型的double类型都是占用两个槽

栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。如上述案例中的test4方法,如下
在这里插入图片描述
我们可以看出方法test4中的变量b在出了括号后就已经无效了,所以b所占有的槽(index=2)就会重新分配给变量c,毕竟局部变量表的本质是一个数组结构,大小不会随笔变动,所以重复利用是最好的版本

总结

 * 变量的分类*    按数据类型分类:基本数据类型和引用数据类型*    按照类中声明位置*      成员变量*        * 类变量(静态成员变量),被类加载器加载后,在link的prepare阶段默认赋值,initalize阶段显示复杂(直接在声明类变量时赋值或者在静态代码快赋值)*        * 实例变量,随着对象的创建在堆中进行赋值,如果没有进行显示赋值(比如构造方法为空或者压根都没有重写构造方法)那就默认赋值*      局部变量:使用前必须进行显示赋值,大家一试便知

补充说明

  • 在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。因为局部变量表有很多引用类型的局部变量,这本质上是引用,执行了堆中真实存储的对象,所以这个地方如果处理不当也是容易引发OOM的;
  • 局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

4.4. 操作数栈(Operand Stack)

每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的 操作数栈,也可以称之为表达式栈(Expression Stack)

操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)

● 某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈
● 比如:执行复制、交换、求和等操作
在这里插入图片描述
操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。

每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max_stack的值。栈中的任何一个元素都是可以任意的Java数据类型

● 32bit的类型占用一个栈单位深度
● 64bit的类型占用两个栈单位深度

操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问

如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令

操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。

另外,我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈

案例代码

  public void testAddOperation() {//byte,short,boolen,short都是按int类型进行保存byte i = 15;int j = 8;int k = i + j;}

反编译其字节码文件如下

 0 bipush 15  2 istore_13 bipush 85 istore_26 iload_17 iload_28 iadd9 istore_3
10 return

执行的第一步bipush命令:bipush命令时把int型变量(byte,short,boolen,short都是按int类型进行保存)入栈,然后pc寄存器保存当前命令行即为0
在这里插入图片描述
istore_1 是把栈顶的操作数保存到局部变量表中索引位置为1的地方,然后pc寄存器保存当前命令行即为2,后续同理,不再对pc寄存器做说明了
在这里插入图片描述
第3,4行命令是继续把j=8这个变量入栈然后存储到到局部变量表中索引位置为2的地方
在这里插入图片描述
在这里插入图片描述

第5,6行iload命令则是分别在局部变量表中索引为1,2的位置分别取出15和8入栈
在这里插入图片描述
在这里插入图片描述
在栈中做运算,执行iadd命令,iadd命令的执行需要依赖于执行引擎调用cpu来运算,然后再把结果入栈
在这里插入图片描述
再把栈中变量值为23的变量k存储到局部变量吧第3个位置上
在这里插入图片描述
然后就是return方法结束
补充:这里是void方法,执行到最后就通过return指令方法就正常结束了,如果是带返回值的类型,那么就里的return xxx除了起到结束方法(结束方法也意味着该方法对应栈帧的局部变量表和操作数栈的清空)的作用(同return)还会把返回值xxx返回给他的上一个调用方法的栈中,也是先入栈,随后通常会再存到局部变量表,这里也要注意

    public int testAddOperation() {byte i = 15;int j = 8;return 10;}public int testAddOperation2() {byte i = 15;int j = 8;return 10;}public void test(){int i = testAddOperation();testAddOperation2();}

test()是int型方法,第一行我们定义了int i = testAddOperation(),这样会存入局部变量表的第一个位置里,第二行testAddOperation2();由于没有定义局部变量来接收返回值,所以不会存入局部变量表,如下test方法

 0 aload_0   #刚开始时就把上一个栈帧中方法testAddOperation的返回值加载到当前栈帧(test方法)操作数栈中1 invokevirtual #2 <com/sobot/net/jvm/StackTest.testAddOperation : ()I>4 istore_1  #把变量i存入局部变量表的第一个位置里5 aload_0 #把上一个栈帧中方法testAddOperation2的返回值加载到当前栈帧(test方法)操作数栈中6 invokevirtual #3 <com/sobot/net/jvm/StackTest.testAddOperation2 : ()I>9 pop
10 return

前面提过,基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派(instruction dispatch)次数和内存读/写次数。

由于操作数是存储在内存中的,因此频繁地执行内存读/写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM的设计者们提出了栈顶缓存(Tos,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理CPU的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率。

在这里插入图片描述
为什么需要运行时常量池呢?

常量池的作用:就是为了提供一些符号和常量,便于指令的识别

案例

public class DynamicLinkingTest {int num = 10;public void methodA() {System.out.println("MethodA");}public void methodB() {System.out.println("MethodB");methodA();num++;}
}

javap -v命令编译后

public class DynamicLinkingTestminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #9.#20         // java/lang/Object."<init>":()V#2 = Fieldref           #8.#21         // DynamicLinkingTest.num:I#3 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;#4 = String             #24            // MethodA#5 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V#6 = String             #27            // MethodB#7 = Methodref          #8.#28         // DynamicLinkingTest.methodA:()V#8 = Class              #29            // DynamicLinkingTest#9 = Class              #30            // java/lang/Object#10 = Utf8               num#11 = Utf8               I#12 = Utf8               <init>#13 = Utf8               ()V#14 = Utf8               Code#15 = Utf8               LineNumberTable#16 = Utf8               methodA#17 = Utf8               methodB#18 = Utf8               SourceFile#19 = Utf8               DynamicLinkingTest.java#20 = NameAndType        #12:#13        // "<init>":()V#21 = NameAndType        #10:#11        // num:I#22 = Class              #31            // java/lang/System#23 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;#24 = Utf8               MethodA#25 = Class              #34            // java/io/PrintStream#26 = NameAndType        #35:#36        // println:(Ljava/lang/String;)V#27 = Utf8               MethodB#28 = NameAndType        #16:#13        // methodA:()V#29 = Utf8               DynamicLinkingTest#30 = Utf8               java/lang/Object#31 = Utf8               java/lang/System#32 = Utf8               out#33 = Utf8               Ljava/io/PrintStream;#34 = Utf8               java/io/PrintStream#35 = Utf8               println#36 = Utf8               (Ljava/lang/String;)V
{int num;descriptor: Iflags:public DynamicLinkingTest();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: aload_05: bipush        107: putfield      #2                  // Field num:I10: returnLineNumberTable:line 3: 0line 4: 4public void methodA();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #4                  // String MethodA5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8public void methodB();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #6                  // String MethodB5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: aload_09: invokevirtual #7                  // Method methodA:()V12: aload_013: dup14: getfield      #2                  // Field num:I17: iconst_118: iadd19: putfield      #2                  // Field num:I22: returnLineNumberTable:line 10: 0line 11: 8line 12: 12line 13: 22
}
}

我们来分析methodA的调用,首先是调用了命令

 9: invokevirtual #7                  // Method methodA:()V

我们看 #7这个符号引用类似是方法引用 Methodref类型,又引用了 #8和#28

 #7 = Methodref          #8.#28         // DynamicLinkingTest.methodA:()V

#8是引用的class类型,即相当于引用当前的类的一个指针

 #8 = Class              #29            // DynamicLinkingTest

#29这个引用 只能在代表当前类

#29 = Utf8               DynamicLinkingTest

回到#28,即方法名与类型

  #28 = NameAndType        #16:#13        // methodA:()V

#16和#13分别如下,含义很直白标识方法名和类型

  #13 = Utf8               ()V#16 = Utf8               methodA

这就是方法A的完整流程,

总之,类在加载的时候会在编译时把所需要的常量加载到运行时常量池里,然后方法在当前栈帧执行时就会通过一系列的符号引用指向运行时常量池

比如着一些最常见的

  #29 = Utf8               DynamicLinkingTest#30 = Utf8               java/lang/Object#31 = Utf8               java/lang/System#32 = Utf8               out#33 = Utf8               Ljava/io/PrintStream;#34 = Utf8               java/io/PrintStream#35 = Utf8               println#36 = Utf8               (Ljava/lang/String;)V

这样最主要的时提高资源的复用性,因为栈是线程共享的,高并发环境下如果每个栈都要把这些资源加载一份那是不可能做到的,所以通过一个小小的引用地址(起到指针作用)来引用这些资源是很好理解的,运行时常量池对于一个类中的各线程来说时需要共享的,那就比如会存储在一个从类角度上时共享的区域即方法区中

动态链接、方法返回地址、附加信息 : 有些地方被称为帧数据区,每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如:invokedynamic指令

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:字节码文件中描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

在这里插入图片描述
常量池的作用:可以总结为就是为了提供一些符号和常量,便于指令的识别

方法的调用:解析与分配
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关

静态链接

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

动态链接

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

静态链接和动态链接不是名词,而是动词,这是理解的关键。

早期绑定

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

4.8.4. 晚期绑定

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

‘’

public class Aniaml {public void eat(){System.out.println("动物进食");}
}interface Huntable {void hunt();
}class Dog extends Aniaml implements Huntable{@Overridepublic void hunt() {System.out.println("多管闲事");}public void eat(){System.out.println("够吃骨头");}}class Cat extends Aniaml implements Huntable{public Cat(String name) {this();//典型的早期绑定}public Cat() {super();//典型的早期绑定}@Overridepublic void hunt() {System.out.println("天经地义");}public void eat(){super.eat(); //典型的早期绑定System.out.println("猫吃耗子");}}class AniamlTest {public void showAnimal(Aniaml aniaml){aniaml.eat();//表现为晚期绑定}public void showHunt(Huntable hunt){hunt.hunt();//接口那更是晚期绑定}
}

对 AniamlTest进行编译,关注里面的两个方法

showAnimal

0 aload_1
1 invokevirtual #2 <com/sobot/net/jvm/Aniaml.eat : ()V>
4 return

showHunt

0 aload_1
1 invokeinterface #3 <com/sobot/net/jvm/Huntable.hunt : ()V> count 1
6 return

可以看出invokevirtual和 invokeinterface都是典型的虚方法调用,与之相对的是,我们关注Cat类中

两个init方法分别如下

0 aload_0
1 invokespecial #1 <com/sobot/net/jvm/Cat.<init> : ()V>
4 return
0 aload_0
1 invokespecial #2 <com/sobot/net/jvm/Aniaml.<init> : ()V>
4 return

eat方法(主要关注里面对父类方法的调用super.eat();)

 0 aload_01 invokespecial #6 <com/sobot/net/jvm/Aniaml.eat : ()V>4 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>7 ldc #7 <猫吃耗子>9 invokevirtual #5 <java/io/PrintStream.println : (Ljava/lang/String;)V>
12 return

如果被调用的方法在编译期无法被确定下来,只能够在程序运行期根据实际的类型绑定相关的方法,这种绑定方式也就被称之为晚期绑定。随着高级语言的横空出世,类似于Java一样的基于面向对象的编程语言如今越来越多,尽管这类编程语言在语法风格上存在一定的差别,但是它们彼此之间始终保持着一个共性,那就是都支持封装、继承和多态等面向对象特性,既然这一类的编程语言具备多态特悄,那么自然也就具备早期绑定和晚期绑定两种绑定方式。Java中任何一个普通的方法其实都具备虚函数的特征,它们相当于C语言中的虚函数(C中则需要使用关键字virtual来显式定义)。如果在Java程序中不希望某个方法拥有虚函数的特征时,则可以使用关键字final来标记这个方法。

虚方法和非虚方法
如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法。反之即为非虚方法

总结
动态连接 => 晚期绑定 =>虚方法 => 调用虚方法invokeVirtual(子类重写了父类的方法,final方法),invokeinterface(接口方法),这里注意一个坑,fianl方法是非虚方法但字节码中显示依然是使用的invokeVirtual,

静态连接 => 晚期绑定 =>非虚方法 => 调用非虚方法invokestatic(调用静态方法),invokespecial(实例构造方法,私有方法,父类方法),fianl方法(对应invokeVirtual),都是非虚方法都是非虚方法

动态调用指令:invokedynamic:动态解析出需要调用的方法,然后执行
● JVM字节码指令集一直比较稳定,一直到Java7中才增加了一个invokedynamic指令,这是Java为了实现「动态类型语言」支持而做的一种改进。
● 但是在Java7中并没有提供直接生成invokedynamic指令的方法,需要借助ASM这种底层字节码工具来产生invokedynamic指令。直到Java8的Lambda表达式的出现,invokedynamic指令的生成,在Java中才有了直接的生成方式。
● Java7中增加的动态语言类型支持的本质是对Java虚拟机规范的修改,而不是对Java语言规则的修改,这一块相对来讲比较复杂,增加了虚拟机中的方法调用,最直接的受益者就是运行在Java平台的动态语言的编译器。

动态类型语言和静态类型语言两者的区别就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之是动态类型语言。说的再直白一点就是,静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征

我们分别以java,python,js来举例

String str = "abc";
int a = 10;
float f = 10.0f;
long l = 10l;
...............
我们可以看出java定义变量必须指明变量类型,而js则不需要指明变量类型,只需要用var来定义一个变量
var a = 10; 
var str = ‘b’;
................
而python则是更绝
info = 100.0;

案例代码

interface Func{public boolean func(String str);
}public class Lamda{public void method(Func func) {return;}public static void main(String[] args) {Lamda lamda = new Lamda();//invokespecial//创建func的实例传入methodFunc func = str -> {return true;};lamda.method(func);//直接以匿名的方式传入进来lamda.method(str -> {return true;});}
}

在这里插入图片描述
这里可能有些难理解,但结合动态语言的本质特点是编译时不定死类型而是在运行时才考虑类型,在回到我们代码

    Lamda lamda = new Lamda();//invokespecial//创建func的实例传入methodFunc func = str -> {return true;};lamda.method(func);//直接以匿名的方式传入进来lamda.method(str -> {return true;});

编译时我们根本就无法确定等号右边部分(匿名函数表达式)对象的类型,只有在运行时才能获取,这就已经是符合动态语言特点了,所以对应的func这个方法的调用类型为invokedynamic,主要还是因为对调用这个方法的引用func完全无法在编译期间确认下来类型

注意:可能有人感觉虚方法和动态调用比较像,它们间的确有共同点就是,总结为一个字就是晚,即编译期间无法下定论,运行时才见真章,但虚方法是站在方法调用角度的而动态调用是站在对象创建角度来说的,这是本质区别

方法返回地址(return address)
存放调用该方法的pc寄存器的值。一个方法的结束,有两种方式:

● 正常执行完成
● 出现未处理的异常,非正常退出

无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

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

  • 执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口; 一个方法在正常调用完成之后,究竟需要使用哪一个返回指令,还需要根据方法返回值的实际数据类型而定。在字节码指令中,返回指令包含ireturn(当返回值是boolean,byte,char,short和int类型时使用),lreturn(Long类型),freturn(Float类型),dreturn(Double类型),areturn。另外还有一个return指令声明为void的方法,实例初始化方法,类和接口的初始化方法使用。
  • 在方法执行过程中遇到异常(Exception),并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,简称异常完成出口。

方法执行过程中,抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码

案例代码

 public void method1() {vretrun();try {method2();} catch (Exception e) {e.printStackTrace();}}private void method2() throws IOException {FileReader fileReader = new FileReader("d://test.txt");char[] buffer = new char[1024];int len = 0;while((len = fileReader.read(buffer)) != -1) {String s = new String(buffer, 0, len);System.out.println(s);}fileReader.close();}

查看方法1中的异常表,注意方法2是把异常给抛出去了,所以没有异常表,方法2把异常抛给了方法1,方法1没有继续抛给它的上一个调用方法而是通过try-catch进线处理,所以方法1有异常表,如下
在这里插入图片描述
含义是字节码4到8行范围内有捕获到异常那就调整到第11行,然后我们参考
在这里插入图片描述
字节码和行号对应关系发现
在这里插入图片描述
其实含义就是try-catch包裹的代码块中的代码出现问题后直接跳转到catch块内进行处理

本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。

正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

相关文章:

JVM 四虚拟机栈

虚拟机栈出现的背景 由于跨平台性的设计&#xff0c;Java的指令都是根据栈来设计的。不同平台CPU架构不同&#xff0c;所以不能设计为基于寄存器的。优点是跨平台&#xff0c;指令集小&#xff0c;编译器容易实现&#xff0c;缺点是性能下降&#xff0c;实现同样的功能需要更多…...

V103开发笔记1-20250113

2025-01-13 一、应用方向分析 应用项目&#xff1a; PCBFLY无人机项目&#xff08;包括飞控和手持遥控器&#xff09;&#xff1b; 分析移植项目&#xff0c;应用外设资源包括&#xff1a; GPIO, PWM,USART,GPIO模拟I2C/SPI, ADC,DMA,USB等&#xff1b; 二、移植项目的基本…...

Page Assist - 本地Deepseek模型 Web UI 的安装和使用

Page Assist Page Assist是一个开源的Chrome扩展程序&#xff0c;为本地AI模型提供一个直观的交互界面。通过它可以在任何网页上打开侧边栏或Web UI&#xff0c;与自己的AI模型进行对话&#xff0c;获取智能辅助。这种设计不仅方便了用户随时调用AI的能力&#xff0c;还保护了…...

Cookie及Session---笔记

目录 Cookiecookie简介cookiesession的认证方式tpshop完整登录实现-cookie Sessionsession简介session自动管理cookietpshop完整登录实现-sessioncookie和session的区别获取响应结果指定内容 Cookie cookie简介 工程师针对HTTP协议是无连接无状态特性所设计的一种技术&#x…...

【Block总结】DASI,多维特征融合

论文信息 HCF-Net&#xff08;Hierarchical Context Fusion Network&#xff09;是一种新提出的深度学习模型&#xff0c;专门用于红外小目标检测。该论文于2024年3月16日发布&#xff0c;作者包括Shibiao Xu、ShuChen Zheng等&#xff0c;主要研究机构为北京邮电大学。该模型…...

LabVIEW的智能电源远程监控系统开发

在工业自动化与测试领域&#xff0c;电源设备的精准控制与远程管理是保障系统稳定运行的核心需求。传统电源管理依赖本地手动操作&#xff0c;存在响应滞后、参数调节效率低、无法实时监控等问题。通过集成工业物联网&#xff08;IIoT&#xff09;技术&#xff0c;实现电源设备…...

4.PPT:日月潭景点介绍【18】

目录 NO1、2、3、4​ NO5、6、7、8 ​ ​NO9、10、11、12 ​ 表居中或者水平/垂直居中单元格内容居中或者水平/垂直居中 NO1、2、3、4 新建一个空白演示文稿&#xff0c;命名为“PPT.pptx”&#xff08;“.pptx”为扩展名&#xff09;新建幻灯片 开始→版式“PPT_素材.doc…...

《迪拜AI展:探寻中东人工智能发展的璀璨新篇》

迪拜&#xff1a;AI 浪潮下的闪耀明珠 迪拜&#xff0c;这座位于阿拉伯半岛东部、波斯湾东南岸的城市&#xff0c;犹如一颗璀璨的明珠&#xff0c;在中东地区散发着独特的魅力。它是阿拉伯联合酋长国的第二大城市&#xff0c;也是迪拜酋长国的首府 &#xff0c;凭借优越的地理位…...

axios如何利用promise无痛刷新token

目录 需求 需求解析 实现思路 方法一&#xff1a; 方法二&#xff1a; 两种方法对比 实现 封装axios基本骨架 instance.interceptors.response.use拦截实现 问题和优化 如何防止多次刷新token 同时发起两个或以上的请求时&#xff0c;其他接口如何重试 最后完整代…...

R语言 | 使用 ComplexHeatmap 绘制热图,分区并给对角线分区加黑边框

目的&#xff1a;画热图&#xff0c;分区&#xff0c;给对角线分区添加黑色边框 建议直接看0和4。 0. 准备数据 # 安装并加载必要的包 #install.packages("ComplexHeatmap") # 如果尚未安装 library(ComplexHeatmap)# 使用 iris 数据集 #data(iris)# 选择数值列&a…...

TensorFlow 简单的二分类神经网络的训练和应用流程

展示了一个简单的二分类神经网络的训练和应用流程。主要步骤包括&#xff1a; 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与部署 加载和应用已训练的模型 1. 数据准备与预处理 在本例中&#xff0c;数据准备是通过两个 Numpy 数…...

蓝桥杯备考:模拟算法之字符串展开

P1098 [NOIP 2007 提高组] 字符串的展开 - 洛谷 | 计算机科学教育新生态 #include <iostream> #include <cctype> #include <algorithm> using namespace std; int p1,p2,p3; string s,ret; void add(char left,char right) {string tmp;for(char ch left1;…...

[创业之路-282]:《产品开发管理-方法.流程.工具 》-1- 优秀研发体系的特征、IPD关注的4个关键要素、IPD体系的7个特点

目录 一、优秀研发体系的特征 二、IPD关注的4个关键要素 1. 组织管理 2. 市场管理 3. 流程管理 4. 产品管理 三、IPD体系的7个特点 1、产品开发是投资行为&#xff1a; 2、基于市场的产品研发&#xff1a; 3、平台化开发&#xff0c;大平台&#xff0c;小产品&#x…...

Node.js 与 PostgreSQL 集成:深入 pg 模块的应用与实践

title: Node.js 与 PostgreSQL 集成:深入 pg 模块的应用与实践 date: 2025/2/5 updated: 2025/2/5 author: cmdragon excerpt: 随着 JavaScript 在服务器端编程中的兴起,Node.js 已成为构建高性能网络应用程序的重要平台。PostgreSQL 则以其强大的特性以及对复杂数据结构的…...

【Uniapp-Vue3】从uniCloud中获取数据

需要先获取数据库对象&#xff1a; let db uniCloud.database(); 获取数据库中数据的方法&#xff1a; db.collection("数据表名称").get(); 所以就可以得到下面的这个模板&#xff1a; let 函数名 async () > { let res await db.collection("数据表名称…...

LeetCode 0090.子集 II:二进制枚举 / 回溯

【LetMeFly】90.子集 II&#xff1a;二进制枚举 / 回溯 力扣题目链接&#xff1a;https://leetcode.cn/problems/subsets-ii/ 给你一个整数数组 nums &#xff0c;其中可能包含重复元素&#xff0c;请你返回该数组所有可能的 子集&#xff08;幂集&#xff09;。 解集 不能 …...

Pytest+selenium UI自动化测试实战实例

今天来说说pytest吧&#xff0c;经过几周的时间学习&#xff0c;有收获也有疑惑&#xff0c;总之最后还是搞个小项目出来证明自己的努力不没有白费。 环境准备 1 确保您已经安装了python3.x 2 配置python3pycharmselenium2开发环境 3 安装pytest库pip install p…...

黑马点评 - 商铺类型缓存练习题(Redis List实现)

首先明确返回值是一个 List<ShopType> 类型那么我们修改此函数并在 TypeService 中声明 queryTypeList 方法&#xff0c;并在其实现类中实现此方法 GetMapping("list")public Result queryTypeList() {return typeService.queryTypeList();}实现此方法首先需要…...

C++ 创建和配置dll与lib库

C简明教程&#xff08;13&#xff09;创建和配置dll与lib库_怎样生成lib库和dll库-CSDN博客 C 动态库与静态库详解 一、为什么要引入库的概念 在 C 编程中&#xff0c;随着项目规模的不断扩大&#xff0c;代码量也会急剧增加。如果将所有代码都写在一个源文件中&#xff0c;…...

深度剖析 Veo2 工具:解锁 AI 视频创作新境界

在当下这个 AI 技术日新月异的时代,各种 AI 工具如雨后春笋般涌现,让人目不暇接。今天,我就来给大家好好说道说道谷歌旗下的 Veo2,这可是一款在 AI 视频创作领域相当有分量的工具。好多朋友都在问,Veo2 到底厉害在哪?好不好上手?能在哪些地方派上用场?别着急,今天我就…...

LabVIEW自定义测量参数怎么设置?

以下通过一个温度采集案例&#xff0c;说明在 LabVIEW 中设置自定义测量参数的具体方法&#xff1a; 案例背景 ​ 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度&#xff0c;需自定义以下参数&#xff1a; 采样率&#xff1a;1 kHz 输入量程&#xff1a;0~10 V&a…...

JVM执行流程与架构(对应不同版本JDK)

直接上图&#xff08;对应JDK8以及以后的HotSpot&#xff09; 这里主要区分说明一下 方法区于 字符串常量池 的位置更迭&#xff1a; 方法区 JDK7 以及之前的版本将方法区存放在堆区域中的 永久代空间&#xff0c;堆的大小由虚拟机参数来控制。 JDK8 以及之后的版本将方法…...

数据治理项目为什么沦为了PPT工程?

数据治理项目为什么沦为了PPT工程&#xff1f; 数据治理项目为什么沦为PPT工程数据治理项目面临的深层挑战数据治理项目的破局之道 "这个项目明明做了快一年了&#xff0c;怎么感觉还在原地踏步&#xff1f;"数据治理小张最近很烦恼。 整天泡在会议室里&#xff0c;写…...

module ‘matplotlib.cm‘ has no attribute ‘get_cmap‘

目录 解决方法1&#xff1a; 解决方法2&#xff0c;新版api改了&#xff1a; module matplotlib.cm has no attribute get_cmap 报错代码&#xff1a; cmap matplotlib.cm.get_cmap(Oranges) 解决方法1&#xff1a; pip install matplotlib3.7.3 解决方法2&#xff0c;新版…...

HTML5 教程之标签(3)

HTML5 <center> 标签 (已废弃) 定义和用法 <center> 标签对其包围的文本进行水平居中处理。HTML5不支持使用<center>标签&#xff0c;因此有关该标签的更多信息&#xff0c;请参考“HTML <center>标签”部分&#xff01; 示例: <center>这个…...

告别传统办公软件,这款编辑器让你事半功倍!

文章目录 1 界面的多样性2 性能优化3 文档编辑器的新功能4 外部文本支持5 体验感想 ONLYOFFICE最近发布了文档8.2版本&#xff0c;带来了众多新特性和性能改进。作为一名用户和开发者&#xff0c;我对这些更新进行了深入的体验&#xff0c;感受到了不少亮点。 新版本特别强调了…...

AI协助探索AI新构型自动化创新的技术实现

一、AI自进化架构的核心范式 1. 元代码生成与模块化重构 - 代码级自编程&#xff1a;基于神经架构搜索的强化学习框架&#xff0c;AI可通过生成元代码模板&#xff08;框架的抽象层定义、神经元结点-网络拓扑态的编码抽象定义&#xff09;自动组合功能模块。例如&#xff0…...

全能型免费内网穿透工具,全面支持macOS、Windows、Linux及Docker系统

1. 登陆官网网址并注册帐号 ngrok | API Gateway, Kubernetes Networking Secure Tunnels 2 下载并安装工具 3. 启动工具 在命令行执行 ngrok http http://localhost:8080 其中端口可换成用户自己想要穿透的端口 4. 获取穿透地址 命令执行后会出现如下画面&#xff0c;红…...

Web - CSS3浮动定位与背景样式

概述 这篇文章主要介绍了 CSS3 中的浮动定位、背景样式、变形效果等内容。包括 BFC 规范与创建方法、浮动的功能与使用要点、定位的多种方式及特点、边框与圆角的设置、背景的颜色、图片等属性、多种变形效果及 3D 旋转等&#xff0c;还提到了浏览器私有前缀。 BFC规范与浏览…...

VUE之组件通信(二)

1、v-model v-model的底层原理&#xff1a;是:value值和input事件的结合 $event到底是啥&#xff1f;啥时候能.target 对于原生事件&#xff0c;$event就是事件对象 &#xff0c;能.target对应自定义事件&#xff0c;$event就是触发事件时&#xff0c;所传递的数据&#xff…...

Gauss高斯:建表语法,存储方式,OLTP和OLAP,系统时间,数组,分组(grouping set,rollup)

数据库和表的语法 数据库 表 oracle,高斯, hive的默认存储方式都是列式存储 存储方式 高斯数据库&#xff08;GaussDB&#xff09;支持列式存储和行式存储 OLTP 与 OLAP OLTP&#xff08;联机事务处理&#xff0c;Online Transaction Processing&#xff09;是一种用于管理…...

Java基础进阶

Java基础进阶 异常 概述 异常就是程序出现了不正常的情况 具体分为&#xff1a;Throwable—>(Error Exception);Exception—>(RuntimeException 非RuntimeException) Throwable类是Java语言中所有错误和异常的祖宗类&#xff1b;&#xff08;上面还有Object类) Thr…...

【数据结构】链表应用1

链表应用 面试题 02.02.返回倒数第k个节点题目描述思路解题过程复杂度 查找相同后缀题目描述解题思路完整代码&#xff1a; 删除绝对值相等的节点题目描述解题思路代码 面试题 02.02.返回倒数第k个节点 题目描述 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回…...

python gltf生成预览图

使用Python生成GLTF模型的预览图 随着3D技术的不断发展&#xff0c;GLTF&#xff08;GL Transmission Format&#xff09;逐渐成为了Web和移动应用程序中最流行的3D文件格式之一。GLTF文件不仅能以较小的体积存储复杂的3D模型&#xff0c;还支持动画、材质、光照和纹理等特性。…...

HTTP和HTTPS协议详解

HTTP和HTTPS协议详解 HTTP详解什么是http协议http协议的发展史http0.9http1.0http1.1http2.0 http协议的格式URI和URL请求request响应response http协议完整的请求与响应流程 HTTPS详解为什么使用HTTPSSSL协议HTTPS通信过程TLS协议 HTTP详解 什么是http协议 1、全称Hyper Tex…...

实战:利用百度站长平台加速网站收录

本文转自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/33.html 利用百度站长平台加速网站收录是一个实战性很强的过程&#xff0c;以下是一些具体的步骤和策略&#xff1a; 一、了解百度站长平台 百度站长平台是百度为网站管理员提供的一系列工…...

专门记录台式电脑常见问题

1、蓝屏死机&#xff0c;检查内存硬盘和cpu 2、拆内存条&#xff0c;用橡皮擦金手指 3、放主板静电&#xff0c;扣主板电池 4、系统时间不正确&#xff0c;主板电池没电 5、开机键坏了 6、电脑主机的风扇转&#xff0c;正常通电运行&#xff0c;但显示器没信号。看键盘的num键&…...

数据库系统概念第六版记录 一

1.关系型数据库 关系型数据库&#xff08;Relational Database&#xff0c;简称 RDB&#xff09;是基于关系模型的一种数据库&#xff0c;它通过表格的形式来组织和存储数据。每个表由若干行&#xff08;记录&#xff09;和列&#xff08;字段&#xff09;组成&#xff0c;数据…...

本地Ollama部署DeepSeek R1模型接入Word

目录 1.本地部署DeepSeek-R1模型 2.接入Word 3.效果演示 4.问题反馈 上一篇文章办公新利器&#xff1a;DeepSeekWord&#xff0c;让你的工作更高效-CSDN博客https://blog.csdn.net/qq_63708623/article/details/145418457?spm1001.2014.3001.5501https://blog.csdn.net/qq…...

Meta Sapiens AI论文解读:人类视觉模型基石初现,AI 未来走向何方?

一、引言 在本文中&#xff0c;我们将深入探讨 Meta AI 的一项新成果&#xff0c;该成果发表于一篇题为《Sapiens&#xff1a;人类视觉模型的基础》的研究论文中。这篇论文介绍了一系列模型&#xff0c;这些模型针对四项以人类为中心的基本任务&#xff0c;正如我们在上面的演示…...

输入类控件和多元素控件【QT】

文章目录 输入类控件QLineEdit Text EditCombo BoxSpin BoxDialSlider多元素控件QListWidget TableWidetTreeWidgetQGroupBoxTab Widget# QVBoxLayout# QHBoxLayoutQGridLayoutQFormLayout 输入类控件 QLineEdit 例如&#xff1a; 实现一个用户输入姓名 密码 电话 性别 的功能…...

一键开启/关闭deepseek

一键开启/关闭 Deepseek对应下载的模型一键开启 Deepseek&#xff0c;一键关闭Deepseek双击对应的bat&#xff0c;就可以启动https://mbd.pub/o/bread/Z56YmpZvbat 下载&#xff1a;https://mbd.pub/o/bread/Z56YmpZv 可以自己写下来&#xff0c;保存成bat文件&#xff0c;也可…...

gitea - fatal: Authentication failed

文章目录 gitea - fatal: Authentication failed概述run_gitea_on_my_pkm.bat 笔记删除windows凭证管理器中对应的url认证凭证启动gitea服务端的命令行正常用 TortoiseGit 提交代码备注END gitea - fatal: Authentication failed 概述 本地的git归档服务端使用gitea. 原来的用…...

Spring AI 智能体通过 MCP 集成本地文件数据

作者&#xff1a;刘军 Model Context Protocol&#xff08;MCP&#xff09;简介 模型上下文协议&#xff08;即 Model Context Protocol&#xff0c;MCP&#xff09; [ 1] 是一个开放协议&#xff0c;它规范了应用程序如何向大型语言模型&#xff08;LLM&#xff09;提供上下…...

音视频入门基础:RTP专题(5)——FFmpeg源码中,解析SDP的实现

一、引言 FFmpeg源码中通过ff_sdp_parse函数解析SDP。该函数定义在libavformat/rtsp.c中&#xff1a; int ff_sdp_parse(AVFormatContext *s, const char *content) {const char *p;int letter, i;char buf[SDP_MAX_SIZE], *q;SDPParseState sdp_parse_state { { 0 } }, *s1…...

MyBatis XML文件配置

目录 一、 配置连接字符串和MyBatis 二、书写持久层代码 2.1 添加Mapper接口 2.2 添加UserlnfoXMLMapper.xml 三、增删改查 3.1 、增&#xff08;Insert&#xff09; 3.2、删&#xff08;Delete) 3.3、改 (Update) 3.4、查 (Select) MyBatisXML的方式需要以下两步&am…...

【Leetcode 热题 100】1143. 最长公共子序列

问题背景 给定两个字符串 t e x t 1 text_1 text1​ 和 t e x t 2 text_2 text2​&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 0 0。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变…...

【算法】动态规划专题④ ——LCS(最长公共子序列)+ LPS(最长回文子序列) python

目录 前置知识LCS举一反三LPS 前置知识 【算法】动态规划专题③ ——二维DP python 子序列定义为&#xff1a; 不改变剩余字符顺序的情况下&#xff0c;删除某些字符或者不删除任何字符形成的一个序列。 LCS 最长公共子序列 https://www.lanqiao.cn/problems/1189/learning/?p…...

Cesium点集中获取点的id,使用viewer.value.entities.getById报错的解决方法

错误代码&#xff1a; viewer.value.entities.getById(pickedObject.id) 报错&#xff1a; 可以正常获取movement.position但是一直出现如下报错&#xff0c;无法获得航点的id&#xff0c;通过断点定位为 viewer.value.entities.getById(pickedObject.id)导致的报错 解决方…...

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问&#xff1a;360手机已停产&#xff0c;现在和以后&#xff0c;能刷机吗&#xff1f; 答&#xff1a;360手机&#xff0c;是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…...