C/C++/ Java程序中的内存可以在堆栈或堆上分配。
先决条件:C 程序的内存布局。
堆栈分配:分配发生在连续的内存块上。我们称之为堆栈内存分配,因为分配发生在函数调用堆栈中。编译器知道要分配的内存大小,每当调用函数,其变量都会在堆栈上分配内存。每当函数调用结束时,变量的内存就会被解除分配。这一切都使用编译器中的一些预定义例程发生。程序员不必担心堆栈变量的内存分配和解除分配。这种内存分配也称为临时内存分配,因为一旦方法完成执行,属于该方法的所有数据就会自动从堆栈中刷新。意味着,只要该方法尚未完成执行且当前处于运行状态,就可以访问存储在堆栈内存方案中的任何值。
关键点:
- 这是一种临时内存分配方案,其中数据成员只有在包含它们的方法()当前正在运行时才可访问。
- 一旦相应的方法完成其执行,它就会自动分配或取消分配内存。
- 我们收到相应的错误Java。朗。 JVM 的 StackOverFlowError,如果堆栈内存已完全填满。
- 与堆内存分配相比,堆栈内存分配被认为更安全,因为存储的数据只能由所有者线程访问。
- 与堆内存分配相比,内存分配和取消分配更快。
- 与堆内存相比,堆栈内存具有更少的存储空间。
CPP
int main()
{
// All these variables get memory
// allocated on stack
int a;
int b[10];
int n = 20;
int c[n];
}
CPP
int main()
{
// This memory for 10 integers
// is allocated on heap.
int *ptr = new int[10];
}
Java
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);
}
}
堆分配:在执行程序员编写的指令期间分配内存。请注意,名称堆与堆数据结构无关。之所以称为堆,是因为它是一堆可供程序员分配和取消分配的内存空间。每次我们创建一个对象时,它总是在堆空间中创建,并且这些对象的引用信息总是存储在堆栈内存中。堆内存分配不如堆栈内存分配安全,因为存储在此空间中的数据对所有线程都是可访问或可见的。如果程序员没有很好地处理这个内存,程序中就会发生内存泄漏。
堆内存分配进一步分为三类:-这三类帮助我们确定要存储在堆内存或垃圾收集中的数据(对象)的优先级。
- 年轻代 –这是内存的一部分,所有新数据(对象)都用于分配空间,每当此内存完全填满时,其余数据将存储在垃圾收集中。
- Old 或 Tenured Generation——这是堆内存的一部分,其中包含不经常使用或根本不使用的较旧数据对象。
- 永久代——这是堆内存的一部分,包含运行时类和应用程序方法的 JVM 元数据。
关键点:
- 如果 Heap-space 已满,我们会收到相应的错误消息Java。 JVM 的 lang.OutOfMemoryError。
- 这种内存分配方案与堆栈空间分配不同,这里没有提供自动解除分配的功能。我们需要使用垃圾收集器来移除旧的未使用对象,以便有效地使用内存。
- 与堆栈内存相比,该内存的处理时间(访问时间)相当慢。
- 堆内存也不像堆栈内存那样是线程安全的,因为存储在堆内存中的数据对所有线程都是可见的。
- 与堆栈内存相比,堆内存的大小要大得多。
- 只要整个应用程序(或Java程序)运行,堆内存就可以访问或存在。
CPP
int main()
{
// This memory for 10 integers
// is allocated on heap.
int *ptr = new int[10];
}
Java堆和栈两种内存分配的混合示例:
Java
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所示:
堆栈和堆分配之间的主要区别
- 在堆栈中,分配和取消分配由编译器自动完成,而在堆中,需要由程序员手动完成。
- 堆帧的处理比栈帧的处理开销更大。
- 内存不足问题更可能发生在堆栈中,而堆内存中的主要问题是碎片。
- 堆栈帧访问比堆帧更容易,因为堆栈具有较小的内存区域并且对缓存友好,但是如果堆帧分散在整个内存中,则会导致更多的缓存未命中。
- 堆栈不灵活,分配的内存大小不能更改,而堆是灵活的,分配的内存可以更改。
- 访问堆的时间比一个栈还多。
对比图
Parameter | STACK | HEAP |
---|---|---|
Basic | Memory is allocated in a contiguous block. | Memory is allocated in any random order. |
Allocation and De-allocation | Automatic by compiler instructions. | Manual by the programmer. |
Cost | Less | More |
Implementation | Easy | Hard |
Access time | Faster | Slower |
Main Issue | Shortage of memory | Memory fragmentation |
Locality of reference | Excellent | Adequate |
Safety | Thread safe, data stored can only be accessed by owner | Not Thread safe, data stored visible to all threads |
Flexibility | Fixed-size | Resizing is possible |
Data type structure | Linear | Hierarchical |