Java 中的栈是否被垃圾回收?

62

在Java中,堆内存是由垃圾回收器进行垃圾回收的。

那么栈内存也被垃圾回收吗?

栈内存是如何被回收的?


10
栈是最初的垃圾回收算法。即使C语言也有一个带垃圾回收的栈! - jrockway
C、C++和Java都有堆栈垃圾回收机制。除此之外,只有Java提供堆垃圾回收机制(在这三种语言中)。 - Siddharth Kamaria
9个回答

43

栈内存包含方法参数和局部变量(确切地说:对象的引用和原始类型的变量本身)。当你离开方法时,它们将被自动删除。如果这些变量是对象的引用,则对象本身位于堆上,并由垃圾回收器处理。

因此,栈不会像堆一样进行垃圾回收,但栈本身是一种自动内存管理方式(早于垃圾回收)。

Thomas Pornin提供了更详细的答案,欲了解更多细节请参考。


32

在Java中,堆栈不会进行垃圾回收。

为方法调用分配的堆栈将在方法返回时释放。由于这只是一个非常简单的LIFO结构,因此不需要进行垃圾回收。

堆栈和垃圾回收交互的一个地方是,堆栈上的引用是GC根(这意味着它们是从中决定可达性的根引用)。


12
准确地说,堆栈中并不包含任何可进行垃圾回收的内容。它只包含对象的引用和原始类型的值。唯一需要进行垃圾回收的是对象。请注意对象和对其的引用之间的区别。 - Roland Illig
本地变量当然也存储在堆栈中,所以你的论点是错误的@RolandIllig。 - MarianP
它们是坐在堆栈中还是堆中? :) - MarianP
2
如果你所说的“they”是指“局部变量”,那么它们存储在栈上。如果你指的是对象,那么它们存储在堆上。而且可能会有引用对象(堆)的局部变量(栈)。 - Joachim Sauer
1
@MarianP:回答有点晚,但是解释一下:假设你有一个局部变量“Integer x = new Integer(42);”。局部变量(x)保存在堆栈上,但它仅是指向包含值42的整数对象的引用。该整数对象在堆上。只有原始类型值(int、double、boolean等)直接保存在堆栈上,但它们不是对象。 - Mnementh
显示剩余3条评论

16

堆栈可能会被垃圾回收。然而,在大多数JVM实现中,它被处理为一个“堆栈”,这意味着它不允许进行垃圾回收。

我们所说的堆栈是方法激活上下文的积累:对于每个调用的方法,这是包含方法参数、本地变量、指向调用方法上下文的隐藏指针和保存指令指针的概念结构。从Java语言本身无法访问激活上下文。当方法退出(使用return或由于抛出异常)时,上下文变得无用。当方法A调用方法B时,保证当A重新获得控制权时,B的上下文已经变得无用。这意味着B的上下文寿命是A上下文寿命的子范围。因此,(对于给定线程的)激活上下文可以按照LIFO(“后进先出”)规则分配。简单来说,就是一个堆栈:新的激活上下文被推到上下文堆栈的顶部,并且在顶部的上下文将首先被处理。

实际上,激活上下文(也称为堆栈帧)按照堆栈顺序连接在专用区域中。该区域在线程启动时从操作系统获取,并在线程终止时将其返还给操作系统。堆栈顶部由特定指针指定,通常包含在CPU寄存器中(这取决于JVM是解释代码还是编译代码)。"指向调用者上下文的指针"是虚拟的;调用者的上下文必须位于堆栈顺序的下方。GC不会介入:堆栈的区域是从线程活动本身同步创建和回收的。这也是许多语言(如C)中的工作方式,它们根本没有GC。
现在没有什么可以阻止JVM实现以其他方式进行操作,例如在堆中分配激活上下文并让GC收集它们。这在Java虚拟机中通常不会发生,因为堆栈分配更快。但是有些其他语言需要做这样的事情,特别是那些在使用GC的同时玩弄continuations的语言(例如Scheme及其call-with-current-continuation函数),因为这些游戏打破了上述LIFO规则。

+1 是为了实际区分方法激活上下文和堆栈。 - sleske
值得一提的是,在某些平台上(尤其是嵌入式系统上),保存的指令指针会放入与函数参数、局部变量等完全不同的存储区域中。对于从不使用间接函数调用的程序(许多嵌入式系统属此类),这可以很好地防止错误代码执行。此外,这种设计可以通过避免在函数进入/退出时花费内存周期来保存或重新加载程序计数器(硬件栈直接操作程序计数器并跳过内存总线)来加快速度。 - supercat

8
内存的堆栈部分就像一个“堆栈”一样工作。我知道听起来很糟糕,但这正是它的工作方式。数据被添加到顶部,彼此叠放(推到堆栈上),并且随着程序运行,会自动从顶部删除(从堆栈中弹出)。它不会被垃圾回收 - 也没有必要,因为一旦数据从堆栈中弹出,该内存就会自动回收。当我说回收时,我并不是指它被解除分配 - 只是堆栈内存中下一个数据将存储的位置会减少,因为数据被弹出。
当然,这并不是说您完全不需要担心堆栈。如果您多次运行递归函数,它最终会使用完所有堆栈空间。如果调用许多函数,特别是如果它们具有许多参数和/或局部变量,则同样如此。
但是底线是,堆栈的内存在函数进入和离开范围时自动使用和回收。因此,在程序执行结束时,所有堆栈内存都将免费,并释放回操作系统。

5
如果您提到堆栈中使用的内存,那么它不会进行垃圾回收。
Java虚拟机使用显式字节码指令来在堆栈上保留和释放内存,这些指令由编译器生成并管理堆栈上原始类型(如int、boolean、double)和对象引用的生命周期。
曾经有计划实现所谓的尾调用优化,一旦知道某些条目不再使用,就会从堆栈中删除它们,但我不知道任何已支持此功能的jvm。
因此,堆栈本身没有垃圾回收,只有编译器生成的推送和弹出指令来管理内存使用情况。
堆栈本身是线程的一部分。当创建线程对象时,分配堆栈,并在线程终止且不再引用线程对象时进行垃圾回收。

1

Java 中的所有对象都分配在堆上。(至少根据规范,如果它们透明地表现为在堆上分配,则实际实现可能将它们分配在堆栈上。)

确切可回收的内容有点微妙。如果一个对象的唯一引用在单个堆栈帧中,并且可以证明该引用不会再次使用,则可以回收该对象。如果对象仅用于读取字段,则该字段读取可能会被优化向前,并且对象的回收可能比您预期的要早。

除非您正在使用 finalizers(或者可能是 References),否则通常不需要担心这个问题。在这种情况下,您应该小心使用锁/易失性来强制执行 happens-before 关系。

当线程停止时,通常整个堆栈将被释放。


0

在编程中,当你在方法中使用内部变量或进行方法调用时,数据会像从堆栈中推入和弹出一样处理,你不需要过多关注这个过程。


0

堆栈上的所有内容都被垃圾回收器视为全局根。因此,可以肯定地说堆栈是“垃圾回收”的。


0
不,Java中的堆栈不会被垃圾回收。每个线程都有自己的堆栈,其中包含:
  1. 方法特定值(短暂存在)和
  2. 对在堆上创建的对象的引用,并且由方法引用
这些值作为堆栈帧推送到每个方法调用的堆栈中。由于堆栈遵循“后进先出”的顺序,在每个方法调用结束时,包含所有方法特定数据和对象引用(如果有)的每个堆栈帧都会弹出。
因此,一旦方法/程序超出范围,堆栈中的数据将自动清除。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接