📜  堆栈与堆内存分配

📅  最后修改于: 2021-09-10 03:03:18             🧑  作者: Mango

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所示:

图。1

堆栈和堆分配之间的主要区别

  1. 在堆栈中,分配和取消分配由编译器自动完成,而在堆中,需要由程序员手动完成。
  2. 堆帧的处理比栈帧的处理开销更大。
  3. 内存不足问题更可能发生在堆栈中,而堆内存中的主要问题是碎片。
  4. 堆栈帧访问比堆帧更容易,因为堆栈具有较小的内存区域并且对缓存友好,但是如果堆帧分散在整个内存中,则会导致更多的缓存未命中。
  5. 堆栈不灵活,分配的内存大小不能更改,而堆是灵活的,分配的内存可以更改。
  6. 访问堆的时间比一个栈还多。

对比图

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