栈与堆内存分配的区别
C/C++/Java 程序中的内存可以分配在堆栈或堆上。
栈分配: 分配发生在连续的内存块上。我们称其为栈内存分配,因为分配发生在函数调用堆栈中。 编译器知道要分配的内存大小,每当调用函数时,它的变量都会在栈上分配内存。 每当函数调用结束时,变量的内存就会被释放。 这一切都发生在编译器中使用一些预定义的例程。 程序员不必担心栈变量的内存分配和解除分配。 这种内存分配也称为临时内存分配,因为一旦方法完成执行,属于该方法的所有数据都会自动从栈中清除。 意味着,只要方法尚未完成执行且当前处于运行状态,就可以访问存储在栈内存方案中的任何值。
关键点:
- 这是一种临时内存分配方案,其中数据成员只有在包含它们的方法当前正在运行时才能访问。
- 一旦相应的方法完成执行,它就会自动分配或取消分配内存。
- 如果堆栈内存已完全填满,则收到相应的JVM
Java. lang. StackOverFlowError
错误。 - 与堆内存分配相比,栈内存分配被认为更安全,因为存储的数据只能由所有者线程访问。
- 与堆内存分配相比,栈内存分配和解除分配更快。
- 与堆内存相比,栈内存的存储空间更少。
int main()
{
// All these variables get memory
// allocated on stack
int a;
int b[10];
int n = 20;
int c[n];
}
堆分配: 在执行程序员编写的指令期间分配内存。 注意 heap
这个名字与 heap
数据结构无关。 之所以称为 堆 ,是因为它是供程序员分配和取消分配的一堆内存空间。每次创建一个对象时,它总是在堆空间中创建,并且这些对象的引用信息总是存储在堆栈内存中。 堆内存分配不如栈内存分配安全,因为存储在该空间中的数据对所有线程都是可访问或可见的。 如果程序员没有很好地处理这块内存,程序中可能会发生内存泄漏。
堆内存分配进一步分为三类:- 这三类帮助我们确定要存储在堆内存或垃圾收集中的数据(对象)的优先级。
- 年轻代 — 这是所有新数据(对象)用于分配空间的内存部分,每当这个内存完全填满时,其余数据就会存储在垃圾收集中。
- 老一代 — 这是堆内存的一部分,其中包含不经常使用或根本不使用的旧数据对象。
- 永久生成 — 这是堆内存的一部分,它包含运行时类和应用程序方法的 JVM 元数据。
关键点:
- 如果堆空间已满,会收到相应的 JVM 的 Java.lang.OutOfMemoryError 错误消息。
- 这种内存分配方案与 Stack-space 分配不同,这里没有提供自动解除分配功能。 我们需要使用垃圾收集器来删除旧的未使用对象,以便有效地使用内存。
- 与栈内存相比,此内存的处理时间(访问时间)非常慢。
- 堆内存也不像栈内存那样是线程安全的,因为存储在堆内存中的数据对所有线程都是可见的。
- 与栈内存相比,堆内存的大小要大得多。
- 只要整个应用程序(或 java 程序)运行,堆内存就可以访问或存在。
int main()
{
// This memory for 10 integers
// is allocated on heap.
int *ptr = new int[10];
}
java中Heap和Stack两种内存分配的混合示例:
class Emp {
int id;
String emp_name;
public Emp(int id, String emp_name) {
this.id = id;
this.emp_name = emp_name;
}
}
public class Emp_detail {
private static Emp Emp_detail(int id, String emp_name) {
return new Emp(id, emp_name);
}
public static void main(String[] args) {
int id = 21;
String name = "Maddy";
Emp person_ = null;
person_ = Emp_detail(id, emp_name);
}
}
以下是我们在分析上述示例后得出的结论:
- 当开始执行 have 程序时,所有运行时类都存储在堆内存空间中。
- 在下一行找到
main()
方法,该方法连同它的所有原始(或本地)存储到堆栈中,并且Emp_detail
类型的引用变量Emp
也将存储在堆栈中并指向相应的对象 存储在堆内存中。 - 然后下一行将调用
main()
中的参数化构造函数Emp(int, String)
,它还将分配到同一个堆栈内存块的顶部。 这将存储:- 堆栈内存的调用对象的对象引用。
- 堆栈内存中的原始值(原始数据类型)int id。
String emp_name
参数的引用变量,它将指向从字符串池到堆内存的实际字符串。
- 然后
main
方法将再次调用Emp_detail()
静态方法,为此将在前一个内存块之上的堆栈内存块中进行分配。 - 因此,对于新创建的
Emp_detail
类型的对象Emp
和所有实例变量将存储在堆内存中。
图示如下图1所示:
栈和堆分配内存的主要区别
- 在栈中,分配和解除分配由编译器自动完成,而在堆中,则需要程序员手动完成。
- 堆帧的处理比栈帧的处理成本更高。
- 内存短缺问题更可能发生在堆栈中,而堆内存中的主要问题是碎片。
- 栈帧访问比堆帧更容易,因为栈具有较小的内存区域并且对缓存友好,但是在堆帧分散在整个内存中的情况下,它会导致更多的缓存未命中。
- 栈不灵活,分配的内存大小无法更改,而堆是灵活的,并且可以更改分配的内存。
- 堆占用的访问时间不仅仅是堆栈。
栈和堆比较表
参数 | 栈 | 堆 |
---|---|---|
基本 | 内存分配在一个连续的块中。 | 内存以任意随机顺序分配。 |
分配和解除分配 | 由编译器指令自动执行 | 程序员手动分配 |
成本 | 更低 | 更多 |
实施 | 容易 | 困难 |
访问时间 | 快 | 慢 |
主要问题 | 内存不足 | 内存碎片 |
参考地址 | 优秀 | 足够 |
安全 | 线程安全,存储的数据只能由所有者访问 | 非线程安全,存储的数据对所有线程可见 |
灵活性 | 固定大小 | 调整是可能的 |
数据类型结构 | 线性 | 分层 |