【问题标题】:What happens to the stack when exiting a method?退出方法时堆栈会发生什么?
【发布时间】:2015-07-10 07:05:03
【问题描述】:

我正在阅读What and where are the stack and heap?。我有点模糊的一件事是方法退出后堆栈会发生什么。以这张图片为例:

退出方法时堆栈被清除,但这意味着什么?堆栈上的指针是否刚刚移回堆栈的开头使其为空?我希望这不是一个太宽泛的问题。当堆栈从退出方法中清除时,我不确定幕后发生了什么。

【问题讨论】:

  • 当方法结束时,为该方法保留的堆栈内存块变为空,因为不再需要(函数结束)并且堆栈指针跳回前一个堆栈块(以便继续你之前处理的函数)。
  • 查看这篇关于 How the Java virtual machine handles method invocation and return 的 17 岁文章。它深入解释了方法调用和返回时发生的情况。 JVM 基础知识在整个时间里不应该发生太大变化。如果有时间,您可以与上述链接的规范进行交叉检查...
  • 这取决于JVM如何实现它。您可以谈论它在概念上是如何完成的,但它是如何真正完成的,可能会在 JVM 和 JVM 版本之间发生变化。 (例如,从概念上讲,所有对象都在堆上分配。但实际上,如果一个对象从未在方法之外被引用,HotSpot JIT 编译器可以决定将其分配在堆栈上;但是在您的代码中,您永远不会注意到区别)

标签: java stack


【解决方案1】:

当调用方法时,局部变量位于堆栈上。 对象引用也存放在栈中,对应的对象存放在堆中。

堆栈只是一个内存区域,它有一个开始和结束地址。 JVM(java虚拟机)有一个寄存器指向当前栈顶(栈指针)。如果调用了新方法,则会在寄存器中添加一个偏移量以获取堆栈上的新空间。

当一个方法调用结束时,堆栈指针会减少这个偏移量,这释放了分配的空间。

局部变量和其他东西(如返回地址、参数...)可能仍在堆栈中,将被下一个方法调用覆盖。

顺便说一句:这就是 java 将所有对象存储在堆中的原因。当一个对象位于堆栈上,并且您将返回指向堆栈的引用时,该对象可能会被下一个方法调用销毁。

【讨论】:

    【解决方案2】:

    在函数执行期间,所有局部变量都在堆栈中创建。这意味着堆栈会增长以为这些变量腾出足够的空间。

    当函数结束时,所有局部变量都超出范围,堆栈被倒回。没有其他事情需要发生,没有隐式归零内存。但是:

    • 从语义上讲,变量超出范围,无法再使用
    • 以困难的方式,堆栈指针被倒回,有效地释放内存:它将被下一个函数调用使用

    上述内容不仅适用于函数,而且对于任何代码块都可以相同,因为语义块中定义的变量在块末尾超出范围。

    【讨论】:

      【解决方案3】:

      考虑一下您的编译代码在机器(或者,对我们人类来说更好的是汇编)级别可能会是什么样子,这可能对您很有用。将其视为 X86 汇编中的一个可能示例:

      当方法被调用时,参数要么在寄存器中传递,要么在堆栈本身中传递。无论哪种方式,调用该方法的代码最终都会:

      call the_method
      

      当这种情况发生时,当前指令指针被压入堆栈。堆栈指针指向它。现在我们在函数中:

      the_method:
         push ebp
         mov  ebp, esp
      

      当前基指针保存在堆栈中,然后基指针用于引用堆栈中的内容(如传入的变量)。

         sub  esp, 8
      

      接下来,在堆栈上分配 8 个字节(假设分配了两个四字节整数)。

         mov [ebp-4], 4
         mov [ebp-8], 2
      

      局部变量被赋值。这实际上可以通过简单地推动它们来完成,但更有可能涉及sub。快进到最后:

         mov esp, ebp
         pop ebp
         ret
      

      当这种情况发生时,堆栈指针会回到我们开始时的位置,指向存储的基指针(保存的帧指针)。这会弹出回 EBP,让 ESP 指向返回指针,然后使用ret“弹出”到 EIP。实际上,堆栈已展开。尽管两个局部变量的实际内存位置没有改变,但它们实际上位于堆栈之上(实际上在内存之下,但我想你明白我的意思。)

      【讨论】:

        【解决方案4】:

        请记住,堆栈是分配给进程的内存区域。

        总而言之,当您在代码中调用函数时(通常使用汇编语言),您需要将要使用的寄存器存储在内存中(如果您遵循另一个合同,它可能会有所不同),因为这些寄存器可以通过调用另一个函数来覆盖(您需要存储返回地址、参数等等,但让我们忽略它)。为此,您将堆栈指针减少该数量的寄存器。在退出之前,您需要确保将堆栈指针增加相同的数字。您无需再做任何事情,因为不再需要您存储的值,它们将被下一个函数调用覆盖。

        在 Java 中,当对象本身位于堆中时,对对象的引用位于堆栈中。如果对某个对象的所有引用都从堆栈中删除,垃圾收集器将从堆中删除该对象。

        希望我的回答对你有所帮助。另外,check this.

        【讨论】:

          【解决方案5】:

          堆栈上的指针是否刚刚移回堆栈的开头使其为空?

          堆栈上的指针被移回函数调用之前的位置。堆栈不会为空,因为它包含属于将程序带到该点的调用的数据。

          举例说明:如果 func1 调用 func2 调用 func3 堆栈将如下所示:

          func1 参数/本地变量... | func2 参数/本地变量... | func3 参数/本地变量...

          func3返回后会是:

          func1 参数/本地变量... | func2 参数/本地变量...

          【讨论】:

            【解决方案6】:

            堆栈就是这样,一堆东西,通常是一堆框架,框架包含参数、局部变量和对象实例,以及其他一些东西,具体取决于您的操作系统。

            如果你在栈上有实例化对象,即 MyClass x 而不是 MyClass * x = new MyClass(),那么当栈倒回到前一帧时,对象 x 将被拆除并调用它的析构函数,这本质上是只是使当前堆栈指针(内部)指向前一帧。在大多数母语中,不会清除内存等。

            最后,这就是为什么您应该初始化局部变量(在大多数语言中),因为对下一个函数的调用将设置一个新帧,该帧很可能与先前重绕的堆栈帧位于同一位置,因此您的局部变量将包含垃圾。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2010-11-22
              • 1970-01-01
              • 2018-02-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-05-28
              相关资源
              最近更新 更多