据我了解,在Java中,堆栈内存保存了原语和方法调用,堆内存用于存储对象。

我有一个类

class A {
       int a ;
       String b;
       //getters and setters
}



a中的原始A会存储在哪里?
为什么堆内存根本存在?为什么我们不能将所有内容都存储在堆栈上?
当对象被垃圾回收时,与对象相关联的堆栈会被破坏吗?


评论

stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heap似乎回答了您的问题。

@ S.Lott,只不过这是关于Java而不是C。

@PéterTörök:同意。虽然代码示例是Java,但没有标签表明它只是Java。而且一般的原则也应该像Java一样适用于Java。此外,有关堆栈溢出的问题有很多答案。
@SteveHaigh:在这个网站上,每个人都太在意某个东西是否属于这里...我想知道这个网站在与问题是否属于这里的所有挑剔中真正得到了什么样的关注。

#1 楼

堆栈和堆之间的基本区别是值的生命周期。

堆栈值仅存在于创建它们的函数范围内。一旦返回,它们将被丢弃。
但是堆值存在于堆中。它们是在某个时间点创建的,而在另一个时间点被销毁(通过GC或手动,具体取决于语言/运行时)。

现在Java仅将原语存储在堆栈中。这样可以使堆栈变小,并有助于使单个堆栈的帧变小,从而允许更多的嵌套调用。
在堆上创建对象,并且仅引用(反过来是基元)在堆栈上传递。

因此,如果创建对象,则将其与所有属于它的变量一起放入堆中,以便它可以在函数调用返回后保留。

评论


“并且只有引用(反过来又是基元)”为什么说引用是基元?你能澄清一下吗?

–极客
2012年7月31日在8:33

@Geek:因为原始数据类型的通用定义适用:“一种由编程语言提供的数据类型作为基本构建块”。您可能还会注意到,本文后面的一些规范示例中列出了引用。

–back2dos
2012年7月31日在8:52

@Geek:就数据而言,您可以将任何原始数据类型(包括引用)视为数字。甚至char是数字,因此可以互换使用。引用也只是指向内存地址的数字,长度为32或64位(尽管不能这样使用-除非您弄乱了sun.misc.Unsafe)。

– Sune Rasmussen
13年4月2日在9:35



这个答案的术语是错误的。根据Java语言规范,引用不是原语。答案的要旨是正确的。 (虽然您可以通过by来声明引用是“某种意义上的”原语。JLS定义了Java的术语,并且它说原语类型为boolean,byte,short,char,int,long,float然后加倍。)

– Stephen C
2014年11月22日在22:42



正如我在本文中发现的那样,Java可能会将对象存储在堆栈上(甚至存储在用于短期对象的寄存器中)。 JVM可以做很多事情。说“现在Java只将原语存储在堆栈中”是不正确的。

–user40980
2014年11月22日23:45

#2 楼

基本字段存储在哪里?
原始字段存储为实例化在某个地方的对象的一部分。想到这是最简单的方法是堆。然而,这并非总是如此。如Java理论和实践中所述:回顾城市性能传奇:

JVM可以使用一种称为转义分析的技术,通过这种技术,他们可以告知某些对象在整个生命周期内都局限于一个线程,并且该生存期受给定堆栈帧的生存期限制。这样的对象可以安全地分配在堆栈而不是堆上。甚至更好的是,对于小型对象,JVM可以完全优化分配,只需将对象的字段提升到寄存器中即可。

,除了说“对象已创建并且字段也在那里”之外,无法说出是堆还是栈中的东西。请注意,对于短寿命的小型对象,“对象”可能不存在于内存中,而是将其字段直接放置在寄存器中。
本文的结论是:

JVM非常擅长弄清我们以前假定只有开发人员才能知道的事情。通过让JVM根据具体情况在堆栈分配和堆分配之间进行选择,我们可以获得堆栈分配的性能优势,而又不会使程序员为在堆栈上分配还是在堆上分配而烦恼。

因此,如果您具有类似以下代码:
void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}

,其中...不允许qux离开该范围,则qux可能会分配在堆栈上。对于VM来说,这实际上是一个胜利,因为这意味着它永远不需要进行垃圾回收-当它离开作用域时,它将消失。
有关Wikipedia逃逸分析的更多信息。对于那些愿意研究论文的人,IBM的Escape Analysis for Java。对于那些来自C#领域的人,您可能会读到Eric Lippert的《堆栈是实现细节》和《关于值类型的真相》(它们对于Java类型也很有用,因为许多概念和方面都相同或相似)。 )。为什么.Net书籍为什么谈论堆栈与堆内存分配?
关于堆栈和堆为何的原因
在堆上为什么
那么,为什么要完全使用堆栈或堆呢?对于超出范围的事物,堆栈可能会很昂贵。请考虑以下代码:
void foo(String arg) {
    bar(arg);
    ...
}

void bar(String arg) {
    qux(arg);
    ...
}

void qux(String arg) {
    ...
}

参数也是堆栈的一部分。在没有堆的情况下,您将在堆栈上传递完整的值集。这对于"foo"和较小的字符串来说很好...但是,如果有人在该字符串中放入巨大的XML文件,会发生什么?每次调用会将整个巨大的字符串复制到堆栈中-这将非常浪费。相反,最好将具有某些生命的对象放在直接作用域之外(传递给另一个作用域,并卡在别人维护的结构等)到另一个称为堆的区域中。
在堆栈上
您不需要堆栈。假设可以编写一种不使用堆栈(任意深度)的语言。我小时候就学过的一种古老的BASIC可以做到,一个人只能执行8个级别的gosub调用,并且所有变量都是全局的-没有堆栈。
堆栈的优点是,当您拥有一个变量时,存在一个范围,当您离开该范围时,将弹出该堆栈框架。它确实简化了存在和不存在的内容。程序移至另一个过程,即新的堆栈框架。程序返回到该过程,并且您已回到查看当前作用域的位置;该程序将离开该过程,并且将释放堆栈中的所有项目。
对于编写运行时代码的人,使用堆栈和堆确实很容易。有许多处理代码的概念和方法,可让使用该语言编写代码的人不必再明确思考它们。
堆栈的性质也意味着它不会变得零散。内存碎片是堆的真正问题。您分配了一些对象,然后垃圾回收了一个中间的对象,然后尝试为下一个要分配的较大对象寻找空间。一团糟。能够将东西放到堆栈上意味着您不必处理它。
当收集到垃圾时,
当收集到垃圾时,它就消失了。但这只是垃圾收集,因为它已经被遗忘了-程序中没有更多对象可以从程序的当前状态进行访问的引用。简化垃圾收集。有很多垃圾收集器-即使在Java中,您也可以使用各种标志(文档)来调整垃圾收集器。这些行为不同,每个人如何做事情的细微差别对于这个答案来说太深了。您可能希望阅读Java垃圾收集基础知识,以更好地了解其中的一些工作原理。堆栈框架弹出。如果堆中有东西,并且从堆中的东西引用了它,那么那时将不会对其进行垃圾回收。
为什么这很重要?
在大多数情况下,这是传统。编写的教科书和编译器类以及各种位文档对堆和堆栈有很大的影响。
但是,当今的虚拟机(JVM和类似的虚拟机)已经竭尽全力以防止对程序员隐藏。除非您用尽了另一个并且需要知道原因(而不是仅仅适当地增加存储空间),否则不要紧。
对象在某个地方,并且在可以放置的地方在适当的时间内可以正确,快速地进行访问。无论是在堆栈上还是在堆上-都没有关系。

#3 楼


在堆中,作为对象的一部分,由堆栈中的指针引用。即。 a和b将彼此相邻存储。
因为如果所有内存都是堆栈内存,它将不再有效。最好在我们开始时有一个小的快速访问区域,并将这些参考项目放在更大的内存区域中。但是,当一个对象只是一个简单的原语,而该原语将占用与指向它的指针相同的堆栈空间时,这是过大的。
是。


评论


我想在第二点说,如果您将对象存储在堆栈上(想象一个包含成千上万个条目的字典对象),那么为了将其传递给函数或从函数返回它,您需要复制每个对象时间。通过使用指向堆中对象的指针或引用,我们仅传递(小)引用。

–斯科特·惠特洛克
2011年4月5日在12:25

我以为3是'no',因为如果该对象正在被垃圾回收,则堆栈中没有指向它的引用。

–卢西亚诺
2011年4月5日13:19

@Luciano-我明白你的意思。我对问题3的理解有所不同。 “同时”或“到那个时候”是隐式的。 ::耸肩::

– pdr
2011年4月5日在16:53

#4 楼


在堆上,除非Java通过转义分析证明这不会影响语义,否则Java会在堆栈上分配类实例作为优化。不过,这是一个实现细节,因此对于所有实际目的,除了微优化之外,答案都是“在堆上”。
必须以先进先出的顺序分配和释放堆栈内存。堆内存可以按任何顺序分配和释放。
当对象被垃圾回收时,堆栈中不再有指向它的引用。如果有的话,它们将使该对象保持活动状态。堆栈基元根本不会被垃圾回收,因为当函数返回时,它们会被自动销毁。


#5 楼

堆栈存储器用于存储局部变量和函数调用。

虽然堆内存用于在Java中存储对象。没关系,对象在代码中创建的位置。


a中的原始class A将存储在哪里?


在这种情况下,原语a与A类对象相关联。因此它在堆内存中创建。


为什么根本没有堆内存?为什么我们不能将所有内容都存储在堆栈中?



在堆栈上创建的变量将超出范围并自动销毁。与堆中的变量相比,分配栈要快得多。
必须由垃圾收集器销毁堆上的变量。
与堆栈中的变量相比,分配速度较慢。
如果您确切地知道在编译之前需要分配多少数据并且它不会太大,那么您将使用堆栈。(原始局部变量存储在堆栈中)
如果不知道,则将使用堆无法确切知道在运行时需要多少数据,或者是否需要分配大量数据。 ?


垃圾收集器在堆内存的范围内工作,因此它会破坏从根到根没有引用链的对象。

#6 楼

为了更好地理解(堆/堆栈内存):