java栈帧_Java-JVM 栈帧(Stack Frame)

一、概述

栈帧位置

JVM 执行 Java 程序时需要装载各种数据到内存中,不同的数据存放在不同的内存区中(逻辑上),这些数据内存区称作运行时数据区(Run-Time Data Areas)。

其中 JVM Stack(Stack 或虚拟机栈、线程栈、栈)中存放的就是 Stack Frame(Frame 或栈帧、方法栈)。

对应关系

一个线程对应一个 JVM Stack。JVM Stack 中包含一组 Stack Frame。线程每调用一个方法就对应着 JVM Stack 中 Stack Frame 的入栈,方法执行完毕或者异常终止对应着出栈(销毁)。

当 JVM 调用一个 Java 方法时,它从对应类的类型信息中得到此方法的局部变量区和操作数栈的大小,并据此分配栈帧内存,然后压入 JVM 栈中。

在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。

c7e90c0b8328c980da56d7a231f1a2d1.png

栈帧结构

一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

局部变量表(Local Variable Table)

在编译程序代码的时候就可以确定栈帧中需要多大的局部变量表,具体大小可在编译后的 Class 文件中看到。

局部变量表的容量以 Variable Slot(变量槽)为最小单位,每个变量槽都可以存储 32 位长度的内存空间。

在方法执行时,虚拟机使用局部变量表完成参数值到参数变量列表的传递过程的,如果执行的是实例方法,那局部变量表中第 0 位索引的 Slot 默认是用于传递方法所属对象实例的引用(在方法中可以通过关键字 this 来访问到这个隐含的参数)。

其余参数则按照参数表顺序排列,占用从 1 开始的局部变量 Slot。

基本类型数据以及引用和 returnAddress(返回地址)占用一个变量槽,long 和 double 需要两个。

操作数栈(Operand Stack)

同样也可以在编译期确定大小。

Frame 被创建时,操作栈是空的。操作栈的每个项可以存放 JVM 的各种类型数据,其中 long 和 double 类型(64位数据)占用两个栈深。

方法执行的过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作(与 Java 栈中栈帧操作类似)。

操作栈调用其它有返回结果的方法时,会把结果 push 到栈上(通过操作数栈来进行参数传递)。

动态链接(Dynamic Linking)

每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。

在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在运行时转化为直接引用,这部分称为动态链接。

返回地址(Return Address)

方法开始执行后,只有 2 种方式可以退出 :方法返回指令,异常退出。

帧数据区(Stack Data)

帧数据区的大小依赖于 JVM 的具体实现。

二、反编译代码

源代码

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packagecom.jvm;/*** 编译:javac com\jvm\StackFrame.java

* 反编译:javap -p -v com\jvm\StackFrame.class*/

public classStackFrame {public static voidmain(String[] args) {

add(1, 2);

}private static int add(int a, intb) {int c = 0;

c= a +b;returnc;

}

}

View Code

反编译后的字节码(去除了不相关字节码)

{

public com.jvm.StackFrame();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."":()V

4: return

LineNumberTable:

line 7: 0

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=2, locals=1, args_size=1

0: iconst_1

1: iconst_2

2: invokestatic #2 // Method add:(II)I

5: pop

6: return

LineNumberTable:

line 9: 0

line 10: 6

private static int add(int, int);

descriptor: (II)I

flags: ACC_PRIVATE, ACC_STATIC

Code:

stack=2, locals=3, args_size=2

0: iconst_0

1: istore_2

2: iload_0

3: iload_1

4: iadd

5: istore_2

6: iload_2

7: ireturn

LineNumberTable:

line 13: 0

line 14: 2

line 15: 6

}

三、字节码解释

主要看 add 方法

# 方法描述

# 括号内为入数类型,这里为两个 int 型入参

# 括号外为返回类型,这里为返回 int 型

descriptor: (II)I

# 方法类型,这里为私有的静态方法

flags: ACC_PRIVATE, ACC_STATIC

# 操作数栈为 2

# 本地变量容量为 3

# 入参个数为 2

stack=2, locals=3, args_size=2

执行 add(1,2) 的过程,最后 ireturn 会将操作数栈栈顶的值返回给调用者

a858082f1775386c85afb037539cba6f.png

LineNumberTable 为代码行号与字节码行号的对应关系

8c1f9ae8df2bcd1ceaaca74873c85230.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值