为什么引用类型存储在堆中?

9
我知道在Java(也许在.net中),原始类型存储在堆栈上,而引用类型存储在堆上。我的问题是,我不理解这种行为的优缺点。为什么我们不能引用堆栈内部的内存位置呢?我在谷歌上搜索了一下,但没有找到合适的解释(也许是我不擅长),如果你们能提供一些见解,我将不胜感激。谢谢。

1
这个问题的答案在这里:http://programmers.stackexchange.com/questions/142024/storage-of-value-types-and-reference-types-in-net - SpaceBison
1
谢谢您的回答,我现在对此有了清晰的认识。 - Muhammad Ahmed AbuTalib
4个回答

10
我知道在Java中(或许在.net也是如此),原始类型被存储在堆栈上,而引用类型被存储在堆上。不,它并不取决于它是原始类型还是引用类型,而是取决于作用域,局部变量分配在堆栈上,成员变量在对象实例化时分配在堆上。请参见Java中的原始类型放在堆栈还是堆上?
我的问题是我不理解这种行为的优缺点。在堆栈上存储的数据只在方法执行时存在。一旦方法完成,所有在堆栈上分配的数据都会被删除。在堆上存储的数据只要不被丢弃就一直存在(在Java的情况下,由垃圾回收器在后台处理)。在其他语言如C/C++中,您需要明确地删除/释放在堆上分配的数据。 请考虑以下代码片段:
String someMethod() {
  int i = 0;
  String result = "Hello";

  i = i + 5;
  return result;
}

在这里,一个原始类型(int i)被创建在栈上,并对其进行了一些计算。一旦方法完成,就无法再访问i,它的值也会丢失。对于result引用,基本上也是如此:引用被分配在堆栈上,但对象(在本例中为字符串对象)则被分配在堆上。通过将引用作为返回值返回,它所引用的对象仍然可以在方法外部使用。

我对你的解释一直很顺利,直到你说“引用被分配在堆栈上,但对象(在这种情况下是字符串对象)被分配在堆上”。我理解方法开始时,整数“i”将占用2个字节(取决于平台),并且在方法完成后会消失。然而,您说“结果”也会在堆栈和堆中分配内存?还是您意味着在方法执行期间只存在指向堆中内存位置的指针? - Muhammad Ahmed AbuTalib
3
@MuhammadAhmedAbuTalib 没错 - 简单来说,引用就是指向对象的指针,这个指针分配在栈上,而对象本身分配在堆上。对象本身可以包含其他基元和其他引用作为成员,在这种情况下,当实例化对象时它们会被分配在堆上。 - Andreas Fester

7

一般情况下,你不能将引用类型存储在栈上,因为栈帧在方法返回时被销毁。如果你保存了一个对象的引用,以便在方法完成后可以取消引用它,那么你将会取消引用一个不存在的栈位置。

HotSpot JVM 可以执行逃逸分析,如果它确定一个对象不可能逃出方法范围,它实际上会在栈上分配该对象。


那么,您是否意味着将数据存储在堆栈或堆中的决定与其是原始类型还是引用类型无关?而是与其是全局还是本地有关?如果是这样,那么我的整个人生都是谎言,我认为无论如何,如果我执行Abc obj = new Abc(),Abc的空间总是会进入堆。 - Muhammad Ahmed AbuTalib
如果它是原始类型,那么它肯定在堆栈上,所以你的“无事可做”是错误的。但是是的,JVM对对象所在的位置有最终决定权,因为这是一个严格的实现细节。这就是为什么我不认为有人会花时间担心它的原因。 - Marko Topolnik
3
你需要区分Abc对象所占用的空间和指向Abc对象的引用(指针)所占用的空间。在Abc obj = new Abc()中,内存被分配到堆上用于存储Abc对象,并且(假设代码行是方法体的一部分)为obj引用分配了栈上的空间。 - Andreas Fester
马尔科,最后一个问题,你是正确的,我不应该担心这些细节,但好奇心会害死猫。如果假设Abc在方法体中,它是局部性质的,并且在整个程序中没有被引用到其他地方,在这种情况下,根据你之前的评论,引用空间和Abc的空间是否都在堆栈中? - Muhammad Ahmed AbuTalib
是的,那是正确的,但请注意还有更多的前提条件,比如永远不要将引用传递给其他方法,因为JIT编译器中的静态代码分析器无法确定该方法可能会做什么,特别是在动态调度的情况下。搜索“HotSpot逃逸分析”以深入了解详细信息。 - Marko Topolnik

2

与之相反,引用类型存储在堆上。

我不知道您所说的部分具体指什么,但请记住,只有对象存储在堆上,而指向这些对象的引用仍然存储在栈上。可能这就是您的疑惑所在。

现在,您还应该注意,只有局部变量存储在栈上,而实例/成员变量存储在堆上。

例如:-

String str = new String("Rohit");  // Local variable

在上述情况下,如果str在某个本地作用域中定义,则该引用将在stack上分配内存,并且它将指向在Heap上创建的新字符串对象。

谢谢您的回答,它既简单又具有描述性。但是请注意,我也想知道“为什么”。您能否请解释一下为什么要使用堆,而不能只使用栈。这是因为栈是主要的“工作区”,随着代码执行而改变其状态,因此不能被视为全局变量的占位符吗? - Muhammad Ahmed AbuTalib
是的,我的参考类型你正确地推断了我的意思,你肯定消除了我所拥有的困惑。但只剩下这一点。 - Muhammad Ahmed AbuTalib
1
每个方法调用都存储在堆栈上。与此同时,存储所有传递给它的参数以及创建的局部变量。现在,一旦执行超出该方法,存储这些参数和局部变量的堆栈就被释放。因此它们的范围也结束了。因此可以有更好的内存管理。如果我们谈到对象,要记住一件事,那就是,在一个范围内创建的对象可以在任何其他范围中使用,如果它持有对该对象的引用。所以,它们不应该存储在堆栈上。 - Rohit Jain
让我们这样理解。当您从另一个方法调用方法时,传递的引用被复制到为该方法创建的堆栈中。它指向与原始引用指向的相同对象。因此,现在,在堆上有两个引用指向同一对象。如果需要更清晰的解释,请询问。 - Rohit Jain
因此,在堆上存储“对象”的一个好处是,您可以从创建它的范围之外访问它们。 - Rohit Jain
感谢您的回答,Rohit Jain。它们帮助我有了清晰的认识。另外,我在班加罗尔有一个同名的非常好的朋友。 :) - Muhammad Ahmed AbuTalib

0
为什么我们不能在堆栈内引用内存位置呢?
其实是可以的,但这是一种内存架构决策。
理论上,如果数据不在堆栈顶部,就无法从堆栈中检索任何数据。但在现实世界中,您需要从程序的任何地方访问某些位置。因此,它不能是堆栈,而被称为堆。
这个链接可能会更详细地解释。

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