据我了解,在Java中,堆栈内存保存了原语和方法调用,而堆内存则用于存储对象。
假设我有一堂课
class A {
int a ;
String b;
//getters and setters
}
a
类中的基元A
将存储在哪里?为什么堆内存根本存在?为什么我们不能将所有内容都存储在堆栈中?
当对象被垃圾回收时,与对象相关联的堆栈是否被破坏?
据我了解,在Java中,堆栈内存保存了原语和方法调用,而堆内存则用于存储对象。
假设我有一堂课
class A {
int a ;
String b;
//getters and setters
}
a
类中的基元A
将存储在哪里?
为什么堆内存根本存在?为什么我们不能将所有内容都存储在堆栈中?
当对象被垃圾回收时,与对象相关联的堆栈是否被破坏?
Answers:
堆栈和堆之间的基本区别是值的生命周期。
堆栈值仅存在于创建它们的函数范围内。一旦返回,它们将被丢弃。
但是堆值存在于堆中。它们是在某个时间点创建的,而在另一个时间点被破坏(通过GC或手动,具体取决于语言/运行时)。
现在,Java仅将原语存储在堆栈中。这样可使堆栈变小,并有助于使各个堆栈帧变小,从而允许进行更多的嵌套调用。
对象是在堆上创建的,并且只有引用(即原语)在堆栈上传递。
因此,如果您创建一个对象,它将与所有属于它的变量一起放入堆中,这样它就可以在函数调用返回后继续存在。
char
是数字,因此可以互换使用。引用也只是指向内存地址的数字,它们可以是32位或64位长(尽管不能这样使用-除非您弄乱了sun.misc.Unsafe
)。
boolean
,byte
,short
,char
,int
,long
,float
和double
。)
基本字段存储为在某个地方实例化的对象的一部分。想想这是哪里的最简单方法是堆。 但是,并非总是如此。如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中-您也可以通过使用各种标志(docs)来调整垃圾收集器。这些标志的行为各不相同,每个人如何做事情的细微差别对此答案来说都太深了。您不妨阅读一下Java垃圾回收基础知识,以更好地了解其中的一些原理。
也就是说,如果在堆栈上分配了某些内容,则不会将其作为一部分进行垃圾回收System.gc()
-当堆栈帧弹出时,将其释放。如果有东西在堆上,并且是从栈中的东西引用的,那么此时将不会进行垃圾回收。
在很大程度上,它是传统。编写的教科书和编译器类以及各种位文档对堆和堆栈有很大的影响。
但是,当今的虚拟机(JVM和类似的虚拟机)已经竭尽全力以防止对程序员隐藏。除非您用尽了另一个并且需要知道原因(而不仅仅是适当地增加存储空间),否则不要紧。
该对象位于某个地方,并且在适当的时间范围内可以正确,快速地对其进行访问。如果它在堆栈或堆上-并不重要。
堆栈存储器用于存储局部变量和函数调用。
而堆内存用于在Java中存储对象。没关系,对象在代码中创建的位置。
基元将存储
a
在哪里class A
?
在这种情况下,原语a与A类对象相关联。因此,它在堆内存中创建。
为什么堆内存根本存在?为什么我们不能将所有内容都存储在堆栈中?
当对象被垃圾回收时,与对象相关联的堆栈是否被破坏?
垃圾收集器在堆内存的范围内工作,因此它会破坏从根到根没有引用链的对象。