「java栈帧」java栈帧存放什么

博主:adminadmin 2022-11-23 06:50:09 66

本篇文章给大家谈谈java栈帧,以及java栈帧存放什么对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

看到书上说java栈和虚拟机栈,这两者有什么关系

栈可以看做是一个容器,专门用来存放东西的容器,这个容器有个特点都是先进后出的。

java栈应该叫做栈帧,其实就是一个方法的信息,里面有局部变量表、操作数栈、动态连接、返回地址、附加信息

虚拟机栈就是一个存放栈帧的栈。

栈帧java中用什么表示

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

每一个栈帧都包括了局部变量表,操作数栈,动态连接,方法返回地址和一些额外的附加信息。在编译代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈 都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现。

jvm栈帧包含哪些内容

当线程执行到某个方法时就会往方线程栈中压入一个帧,称为栈帧,栈帧中包含了方法的局部变量表、操作数栈、返回地址、动态连接等信息

局部变量表:

顾名思义就是用来存储java方法中的局部变量的,在编译期间就会分配方法局部变量表的最大容量,局部变量表以变量槽为单位,每个变量槽可以存储32位及32位以下的变量,具体大小根据变量实际占用内存而定,java的基本类型中除了long和double外其他类型都是32位以下,所以每个变量占用一个变量槽即可,而对于long和double类的变量,会占用两个变量槽,除了基本类型当然还有引用类型,引用类型变量长度JVM并没有明确定义。JVM通过索引的方式来访问变量表中的变量,索引从0开始。变量槽是可以重复使用的,当变量槽所存储的变量已经不在其作用域后,该变量槽就可以被其他变量占用

操作数栈:

用于在方法运行时可以存放以及获取操作数,其所需要的最大栈深度也是在编译期间定下的,在方法刚开始运行时,操作数栈是空的,在方法执行过程中会有各种操作指令往操作数栈中压入和获取内容,也就是出栈/入栈操作,比如一个加法指令对两个数据进行相加,运行到这里前会先将两个数据压入栈中,然后将这两个操作数取出相加;在实际情况中,方法的操作数栈之间并不完全独立,往往会公用部分空间,这样在方法调用时就不需要进行参数复制了

动态连接:

前面说了常量池中会存储方法的符号引用,而每个栈帧中都会存储一个引用,用于指向常量池中该方法对应的符号引用,字节码指令中方法的调用就以方法对应的符号引用为参数来进行,在类加载阶段的解析步骤中,部分符号引用会被解析为直接引用,称为静态解析,在方法的运行过程中,另一部分符号引用会被实时的解析为直接引用,称为动态连接。

被静态解析的条件:方法在运行前就有一个可确定的调用版本,其实也就是编译期就刻意确定改方法有没有可能通过继承或者其他方式被重写,在java中静态方法(与类型直接关联),私有方法(外部不可访问),构造方法,父类方法,final方法,这五种方法的符号引用可以被静态解析都不可能被重写,可以在运行前确定唯一的调用版本,满足被静态解析的条件,称为非虚方法。

方法返回地址:

方法的运行过程中,可能会正常退出,也可能会异常退出,不论是哪种退出方式,在退出后都会要保证其上层调用者可以知道方法退出的位置,以便于程序继续执行,方法的返回地址就是用于确定退出位置的。

Java中为什么栈运行

ava Virtual Machine Stacks,线程私有,生命周期与线程相同,描述的是Java方法执行的内存模型:每一个方法执行的同时都会创建一个栈帧(Stack Frame),由于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法的执行就对应着栈帧在虚拟机栈中的入栈,出栈过程。

局部变量表:

存放编译期可知的各种基本数据类型、对象引用类型和returnAddress类型(指向一条字节码指令的地址:函数返回地址)。

long、double占用两个局部变量控件Slot。

局部变量表所需的内存空间在编译期确定,当进入一个方法时,方法在栈帧中所需要分配的局部变量控件是完全确定的,不可动态改变大小。

异常:线程请求的栈帧深度大于虚拟机所允许的深度---StackOverFlowError,如果虚拟机栈可以动态扩展(大部分虚拟机允许动态扩展,也可以设置固定大小的虚拟机栈),但是无法申请到足够的内存---OutOfMemorError。

操作数栈:

后进先出LIFO,最大深度由编译期确定。栈帧刚建立使,操作数栈为空,执行方法操作时,操作数栈用于存放JVM从局部变量表复制的常量或者变量,提供提取,及结果入栈,也用于存放调用方法需要的参数及接受方法返回的结果。

操作数栈可以存放一个jvm中定义的任意数据类型的值。

在任意时刻,操作数栈都一个固定的栈深度,基本类型除了long、double占用两个深度,其它占用一个深度

动态连接:

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用,一部分会在类加载阶段或第一次使用的时候转化为直接引用(如final、static域等),称为静态解析,另一部分将在每一次的运行期间转化为直接引用,这部分称为动态连接。

方法返回地址:

当一个方法被执行后,有两种方式退出该方法:执行引擎遇到了任意一个方法返回的字节码指令或遇到了异常,并且该异常没有在方法体内得到处理。无论采用何种退出方式,在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行。方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态。一般来说,方法正常退出时,调用者的PC计数器的值就可以作为返回地址,栈帧中很可能保存了这个计数器值,而方法异常退出时,返回地址是要通过异常处理器来确定的,栈帧中一般不会保存这部分信息。

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

"栈"和"栈帧"这两个概念到底如何区分

1、栈:FILO先进后出的数据结构

栈底是第一个进栈的数据的位置(压箱 底)

栈顶是最后一个进栈的数据位置

2、根据SP指针指向的位置,栈可分为 满栈和空栈

满栈:当sp指针总是指向最后压入堆栈 的数据(ARM采用满栈)

空栈:当堆栈指针SP总是指向下一个将 要放入数据的空位置。

3、根据SP指针移动的方向,可分为升 栈和降栈

升栈:随数据的入栈,SP由低地址-- 高地址

降栈:随数据的入栈,SP由高地址-- 低地址(ARM采用降栈)

4、栈帧:存储在用户栈上的(当然内核栈同样适用)每一次函数调用涉及的相关信息的记录单元 ; 栈帧(stack frame)就是一个函数所使用的那部分栈,所有函数的栈帧串起来就组成了一个完整的栈。

栈帧的两个边界分别有FP(R11)和SP(R13)L来限定。

栈帧

栈的作用:

1)保存局部变量

分析代码:

[html] view plain copy

#include stdio.h

int main()

{

int a;

a++;

return a;

}/span

反汇编之后的代码;

[html] view plain copy

stack: file format elf32-littlearm

Disassembly of section .text:

00000000 main:

#include stdio.h

int main()

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!) @将栈帧底部指针FP压入栈中;创建属于main函数的栈帧。

4:   e28db000 add    fp, sp, #0  ; 0x0 @fp指针为函数栈帧的底部,

8:   e24dd00c sub    sp, sp, #12 ; 0xc   @sp指针为栈帧的顶部,同时为栈的栈顶。

int a;

a++;

c:   e51b3008 ldr    r3, [fp, #-8]   @由此三句可知变量a在栈帧中执行了加法操作,及栈帧具有保存局部变量的作用

10:   e2833001 add    r3, r3, #1  ; 0x1

14:   e50b3008 str    r3, [fp, #-8]

return a;

18:   e51b3008 ldr    r3, [fp, #-8]

}

/span

2)保存函数的参数

分析代码:

[html] view plain copy

span style="font-size:18px;"#include stdio.h

void func1(int a,int b,int c,int d,int e,int f)

{

int k;

k=e+f;

}

int main()

{

func1(1,2,3,4,5,6);

return 0;

}

反汇编之后的代码;

void func1(int a,int b,int c,int d,int e,int f) @多于4个参数

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!)@保存main函数的栈帧底部指针FP

4:   e28db000 add    fp, sp, #0  ; 0x0

8:   e24dd01c sub    sp, sp, #28 ; 0x1c @由栈帧顶部指针SP创建一片栈帧保存子函数的前四个参数

c:   e50b0010 str    r0, [fp, #-16]  @ a

10:   e50b1014 str    r1, [fp, #-20]  @ b

14:   e50b2018 str    r2, [fp, #-24]  @ c

18:   e50b301c str    r3, [fp, #-28]  @ d

int k;

k=e+f;

1c:   e59b3004 ldr    r3, [fp, #4]    @在子函数的栈帧中实现第五个参数与第六个参数的运算

20:   e59b2008 ldr    r2, [fp, #8] @由ldr  r2, [fp, #8]知参数保存在main函数的栈帧中,并运算

24:   e0833002 add    r3, r3, r2   @以子函数的栈帧底部指针(fp)做参考坐标实现对参数的查找

28:   e50b3008 str    r3, [fp, #-8]

}

2c:   e28bd000 add    sp, fp, #0  ; 0x0

30:   e8bd0800 pop    {fp}

34:   e12fff1e bx lr

00000038 main:

int main()

{

38:   e92d4800 push   {fp, lr}    @由于调用子函数,先保存main函数的栈帧底部指针FP和返回地址LR(当前PC指针的下一地址)

3c:   e28db004 add    fp, sp, #4  ; 0x4 @可知先压入FP,后压入lr.把此时子函数(被调用者)的栈帧底部指针FP指向保存在子函数栈帧的main函数(调用者)的栈帧底部指针FP

40:   e24dd008 sub    sp, sp, #8  ; 0x8   @创建栈

func1(1,2,3,4,5,6);

44:   e3a03005 mov    r3, #5  ; 0x5

48:   e58d3000 str    r3, [sp]

4c:   e3a03006 mov    r3, #6  ; 0x6

50:   e58d3004 str    r3, [sp, #4]

54:   e3a00001 mov    r0, #1  ; 0x1 @用通用寄存器保存前四个参数的值

58:   e3a01002 mov    r1, #2  ; 0x2

5c:   e3a02003 mov    r2, #3  ; 0x3

60:   e3a03004 mov    r3, #4  ; 0x4

64:   ebfffffe bl 0 func1

return 0;

68:   e3a03000 mov    r3, #0  ; 0x0

}

6c:   e1a00003 mov    r0, r3

70:   e24bd004 sub    sp, fp, #4  ; 0x4

74:   e8bd4800 pop    {fp, lr}

78:   e12fff1e bx lr/span

注:C中,若函数的参数小于等于4个,则用通用寄存器保存其参数值,多于4个的参数保存在栈中

3)保存寄存器的值

分析代码:

[html] view plain copy

span style="font-size:18px;"include stdio.h

void func2(int a,int b)

{

int k;

k=a+b;

}

void func1(int a,int b)

{

int c;

func2(3,4);

c=a+b;

}

int main()

{

func1(1,2);

return 0;

}/span

反汇编之后的代码;

[html] view plain copy

span style="font-size:18px;"void func2(int a,int b)

{

0:   e52db004 push   {fp}     ; (str fp, [sp, #-4]!)

4:   e28db000 add    fp, sp, #0  ; 0x0

8:   e24dd014 sub    sp, sp, #20 ; 0x14

c:   e50b0010 str    r0, [fp, #-16] @保存寄存器的值

10:   e50b1014 str    r1, [fp, #-20]

int k;

k=a+b;

14:   e51b3010 ldr    r3, [fp, #-16]

18:   e51b2014 ldr    r2, [fp, #-20]

1c:   e0833002 add    r3, r3, r2

20:   e50b3008 str    r3, [fp, #-8]

}

24:   e28bd000 add    sp, fp, #0  ; 0x0

28:   e8bd0800 pop    {fp}

2c:   e12fff1e bx lr

00000030 func1:

void func1(int a,int b)

{

30:   e92d4800 push   {fp, lr}

34:   e28db004 add    fp, sp, #4  ; 0x4

38:   e24dd010 sub    sp, sp, #16 ; 0x10

3c:   e50b0010 str    r0, [fp, #-16] @代码44行调用func2函数后,又使用r0\r1保存参数,所以此时将r0\r1寄存器的

40:   e50b1014 str    r1, [fp, #-20]  @值放入栈中

int c;

func2(3,4);

44:   e3a00003 mov    r0, #3  ; 0x3

48:   e3a01004 mov    r1, #4  ; 0x4

4c:   ebfffffe bl 0 func2

c=a+b;

50:   e51b3010 ldr    r3, [fp, #-16]

54:   e51b2014 ldr    r2, [fp, #-20]

58:   e0833002 add    r3, r3, r2

5c:   e50b3008 str    r3, [fp, #-8]

}

60:   e24bd004 sub    sp, fp, #4  ; 0x4

64:   e8bd4800 pop    {fp, lr}

68:   e12fff1e bx lr

0000006c main:

int main()

{

6c:   e92d4800 push   {fp, lr}

70:   e28db004 add    fp, sp, #4  ; 0x4

func1(1,2);

74:   e3a00001 mov    r0, #1  ; 0x1

78:   e3a01002 mov    r1, #2  ; 0x2

7c:   ebfffffe bl 30 func1

return 0;

80:   e3a03000 mov    r3, #0  ; 0x0

}

84:   e1a00003 mov    r0, r3

88:   e24bd004 sub    sp, fp, #4  ; 0x4

8c:   e8bd4800 pop    {fp, lr}

90:   e12fff1e bx lr/span

初始化栈:即对SP指针赋予一个内存地址(统一标准:2440、6410、210)

在内存的64MB位置即ldr sp, =0x34000000(2440)

ldr sp, =0x54000000(6410)

ldr sp, =0x24000000(210)

由上可知ARM采用满栈(指向刚入栈的数据)、降栈(由高地址向低地址入栈)

问题:因为ARM不同工作模式有不同的栈,定义栈的技巧是什么,避免定义相同的地址使用不同栈?

转自:

关于java栈帧和java栈帧存放什么的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

The End

发布于:2022-11-23,除非注明,否则均为首码项目网原创文章,转载请注明出处。