退出方法时堆栈会发生啥?
Posted
技术标签:
【中文标题】退出方法时堆栈会发生啥?【英文标题】:What happens to the stack when exiting a method?退出方法时堆栈会发生什么? 【发布时间】:2015-07-10 07:05:03 【问题描述】:我正在阅读What and where are the stack and heap?。我有点模糊的一件事是方法退出后堆栈会发生什么。以这张图片为例:
退出方法时堆栈被清除,但这意味着什么?堆栈上的指针是否刚刚移回堆栈的开头使其为空?我希望这不是一个太宽泛的问题。当堆栈从退出方法中清除时,我不确定幕后发生了什么。
【问题讨论】:
docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6 当方法结束时,为该方法保留的堆栈内存块变为空,因为不再需要(函数结束)并且堆栈指针跳回前一个堆栈块(以便继续你之前处理的函数)。 查看这篇关于 How the Java virtual machine handles method invocation and return 的 17 岁文章。它深入解释了方法调用和返回时发生的情况。 JVM 基础知识在整个时间里不应该发生太大变化。如果有时间,您可以与上述链接的规范进行交叉检查... 这取决于JVM如何实现它。您可以谈论它在概念上是如何完成的,但它是如何真正完成的,可能会在 JVM 和 JVM 版本之间发生变化。 (例如,从概念上讲,所有对象都在堆上分配。但实际上,如果一个对象从未在方法之外被引用,HotSpot JIT 编译器可以决定将其分配在堆栈上;但是在您的代码中,您永远不会注意到区别) 【参考方案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 将被拆除并调用它的析构函数,这本质上是只是使当前堆栈指针(内部)指向前一帧。在大多数母语中,不会清除内存等。
最后,这就是为什么您应该初始化局部变量(在大多数语言中),因为对下一个函数的调用将设置一个新帧,该帧很可能与先前重绕的堆栈帧位于同一位置,因此您的局部变量将包含垃圾。
【讨论】:
以上是关于退出方法时堆栈会发生啥?的主要内容,如果未能解决你的问题,请参考以下文章