退出方法时堆栈会发生什么?

时间:2015-04-30 14:12:59

标签: java stack

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

Stack

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

6 个答案:

答案 0 :(得分:2)

调用函数时,局部变量位于堆栈中。 对象引用也存储在堆栈中,相应的对象存储在堆中。

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

当函数调用结束时,堆栈指针将减少此偏移量,这将释放分配的空间。

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

BTW:这就是java将所有对象存储在堆中的原因。当一个对象位于堆栈上,并且您将返回指向堆栈的引用时,该对象可能会被下一个函数调用破坏。

答案 1 :(得分:1)

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

当函数结束时,所有局部变量都超出范围并重新堆叠堆栈。没有其他事情需要发生,没有隐含的归零记忆。但是:

  • 语义上变量超出范围且无法再使用
  • 以艰难的方式,重新启动堆栈指针,有效释放内存:下一个函数调用可以使用它

上面不仅适用于函数,而且对于任何代码块都可以是相同的,因为语义上块中定义的变量在块结束时超出范围。

答案 2 :(得分:1)

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

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

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

我希望我的回答可以帮到你。另外,check this.

答案 3 :(得分:1)

对于您来说,考虑一下您的编译代码在计算机上的外观(或者更适合我们人类,程序集)的水平可能会有用。将此视为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 :(得分:0)

  

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

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

为了说明:如果func1调用func2调用func3,那么堆栈将如下所示:

func1 args / local vars ... | func2 args / local vars ... | func3 args / local vars ...

func3返回后将是:

func1 args / local vars ... | func2 args / local vars ...

答案 5 :(得分:0)

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

如果您在堆栈上实例化了对象,即MyClass x而不是MyClass * x = new MyClass(),那么当堆栈重绕到前一帧时,对象x将被拆除并调用其析构函数只是使当前堆栈指针(内部)指向前一帧。在大多数本地语言中,不会清除任何内存等。

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