将变量推送到堆栈中的Stack和Variables差异?

所以我知道存在2个内存区域: StackHeap

我也知道,如果你创建一个局部变量,它将存在于堆栈中,而不是堆中。 随着我们将数据推入其中,堆栈将会增长,如:

在此处输入图像描述

现在我将尝试通过我对你的困惑:

例如,这个简单的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中每个线程都有一个堆栈。 每个堆栈由几个框架组成:每个方法调用都会创建一个新框架,并且在完成方法调用时,框架将被销毁。

在堆栈框架内有两个区域:

  1. 操作数堆栈 (这里不要将“堆栈”这个词与JVM堆栈本身混淆 – 这里的堆栈表示该区域为后进先出结构)。
  2. 一组局部变量 ,其中每个变量都有一个索引(从零开始)。

根据JVM实现,它们在内存中可能连续也可能不连续。 从逻辑上讲,它们是堆栈框架的两个独立部分。

aconst_null的描述中aconst_nullaconst_null指令将null对象引用推送到操作数堆栈

并且如astore_ (其中n可以是0,1,2或3)的描述中所述:

必须是当前帧的局部变量数组的索引(第2.6节)。 操作数堆栈顶部的objectref必须是returnAddress类型或类型为reference它从操作数堆栈中弹出, 处的局部变量值设置为objectref

因此,在您的示例中,语句Object foo = null转换为以下内容:

  1. null (指向“nothing”的特殊引用)推到操作数堆栈的顶部。
  operand stack __________ | null | <-- null is pushed on the operand stack |__________| | | |__________| | | |__________| 
  1. 从操作数堆栈弹出引用并将其存储在索引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件事:

  1. 局部变量表
  2. 操作数堆栈
  3. 对类的常量池AKA帧数据的引用

因此, push a null reference onto the stack – >将引用推送到操作数堆栈

store a reference into local variable 1 – >将引用存储到局部变量表的槽1中

您可以将操作数堆栈视为临时变量。 它是每个方法调用的本地,它的大小可以在编译时确定。

如果要对任何类型的变量(局部变量,静态变量或非静态变量)执行任何操作,可以通过操作数堆栈执行此操作。 Java字节码指令主要用于操作数堆栈。

例如,

  • foo = bar将对应于aload_2astore_1 ,这只是意味着将局部变量2的值推送到操作数堆栈并将操作数堆栈 顶部的任何内容弹出到局部变量1
  • if (foo == null) ...对应于aload_1ifnonnull 5 ,后者告诉JVM: 如果操作数堆栈顶部的任何内容不为null,则跳转到下一个5指令偏移量; 否则,继续下一条指令
  • int x = args.length对应于aload_0arraylengthistore_3 ,这意味着推送局部变量0在操作数堆栈顶部弹出数组并将其长度推回弹出整数并将其存储在局部变量3中
  • 诸如iaddisubimulidiv类的数值运算从操作数堆栈中弹出两个整数值并将结果推回
  • 调用方法时,弹出操作数堆栈并将其作为参数传递给新方法的局部变量。
  • 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 ---+