将变量推送到堆栈中的Stack和Variables差异?
所以我知道存在2个内存区域: Stack和Heap 。
我也知道,如果你创建一个局部变量,它将存在于堆栈中,而不是堆中。 随着我们将数据推入其中,堆栈将会增长,如:
现在我将尝试通过我对你的困惑:
例如,这个简单的Java代码:
public class TestClass { public static void main(String[] args) { Object foo = null; Object bar = null; } }
被翻译成这个字节码:
public static void main(java.lang.String[]); Code: Stack=1, Locals=3, Args_size=1 0: aconst_null 1: astore_1 2: aconst_null 3: astore_2 4: return LineNumberTable: line 5: 0 line 6: 2 line 7: 4 LocalVariableTable: Start Length Slot Name Signature 0 5 0 args [Ljava/lang/String; 2 3 1 foo Ljava/lang/Object; 4 1 2 bar Ljava/lang/Object;
根据定义, acons_null是:
push a null reference onto the stack
而astore_1是:
store a reference into local variable 1
我遇到的困惑是,我们将foo推入堆栈,然后我们将它再次存储在堆栈中? 将引用存储在局部变量中意味着什么? 该局部变量在哪里生活? 我们将foo推入或者是这些单独的堆栈?
现在,如果我在第一个对象上调用一个方法,我将其推入堆栈,因为堆栈指针指向我推送的最后一个元素,它将如何处理?
JVM中每个线程都有一个堆栈。 每个堆栈由几个框架组成:每个方法调用都会创建一个新框架,并且在完成方法调用时,框架将被销毁。
在堆栈框架内有两个区域:
- 操作数堆栈 (这里不要将“堆栈”这个词与JVM堆栈本身混淆 – 这里的堆栈表示该区域为后进先出结构)。
- 一组局部变量 ,其中每个变量都有一个索引(从零开始)。
根据JVM实现,它们在内存中可能连续也可能不连续。 从逻辑上讲,它们是堆栈框架的两个独立部分。
如aconst_null
的描述中aconst_null
, aconst_null
指令将null
对象引用推送到操作数堆栈 。
并且如astore_
(其中n
可以是0,1,2或3)的描述中所述:
必须是当前帧的局部变量数组的索引(第2.6节)。 操作数堆栈顶部的
objectref
必须是returnAddress
类型或类型为reference
。 它从操作数堆栈中弹出,处的局部变量值设置为
objectref
。
因此,在您的示例中,语句Object foo = null
转换为以下内容:
- 将
null
(指向“nothing”的特殊引用)推到操作数堆栈的顶部。
operand stack __________ | null | <-- null is pushed on the operand stack |__________| | | |__________| | | |__________|
- 从操作数堆栈弹出引用并将其存储在索引1的局部变量中。此局部变量对应于
foo
。
operand stack local variables __________ _______________ _______________ _______________ _______________ | | | args | foo (null) | | | |__________| |_______0_______|_______1_______|_______2_______|_______3_______| | | store null in LV#1 |__________| | | |__________|
Object bar = null
相同的步骤,除了null
存储在索引2的局部变量中。
来源:Java虚拟机规范(参见本节 )。
你应该看看Java堆栈框架的结构 。
java 堆栈框架包含3件事:
- 局部变量表
- 操作数堆栈
- 对类的常量池AKA帧数据的引用
因此, push a null reference onto the stack
– >将引用推送到操作数堆栈 。
store a reference into local variable 1
– >将引用存储到局部变量表的槽1中
您可以将操作数堆栈视为临时变量。 它是每个方法调用的本地,它的大小可以在编译时确定。
如果要对任何类型的变量(局部变量,静态变量或非静态变量)执行任何操作,可以通过操作数堆栈执行此操作。 Java字节码指令主要仅用于操作数堆栈。
例如,
-
foo = bar
将对应于aload_2
和astore_1
,这只是意味着将局部变量2的值推送到操作数堆栈并将操作数堆栈 顶部的任何内容弹出到局部变量1 -
if (foo == null) ...
对应于aload_1
和ifnonnull 5
,后者告诉JVM: 如果操作数堆栈顶部的任何内容不为null,则跳转到下一个5指令偏移量; 否则,继续下一条指令 。 -
int x = args.length
对应于aload_0
,arraylength
,istore_3
,这意味着推送局部变量0 , 在操作数堆栈顶部弹出数组并将其长度推回 , 弹出整数并将其存储在局部变量3中 - 诸如
iadd
,isub
,imul
,idiv
类的数值运算从操作数堆栈中弹出两个整数值并将结果推回 - 调用方法时,弹出操作数堆栈并将其作为参数传递给新方法的局部变量。
-
putstatic
/getstatic
pops /推送到静态变量/从静态变量推送 -
putfield
/getfield
弹出/推送到非静态变量/从非静态变量推送
它是相同的堆栈。
或者至少你可以认为它是同一个堆栈,它实际上取决于jvm实现。
在一个简单的jvm
调用方法时,它会为堆栈上的局部变量保留空间。 它基本上将堆栈指针递增到它的局部变量的开放空间。 方法的父对象(如果是实例方法)和方法的参数是第一个本地对象。
要将堆栈中的内容分配给本地var,需要从堆栈顶部复制到相邻内存区域之前的几个位置。
在你的例子中,在astore 1
期间:
locals/stack [local 0] // args [local 1] // foo <--+ [local 2] // bar | ..return address.. | [stack 0] // null ---+