栈与堆内存分配的区别

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所示:
栈与堆内存分配的区别

栈和堆分配内存的主要区别

  • 在栈中,分配和解除分配由编译器自动完成,而在堆中,则需要程序员手动完成。
  • 堆帧的处理比栈帧的处理成本更高。
  • 内存短缺问题更可能发生在堆栈中,而堆内存中的主要问题是碎片。
  • 栈帧访问比堆帧更容易,因为栈具有较小的内存区域并且对缓存友好,但是在堆帧分散在整个内存中的情况下,它会导致更多的缓存未命中。
  • 栈不灵活,分配的内存大小无法更改,而堆是灵活的,并且可以更改分配的内存。
  • 堆占用的访问时间不仅仅是堆栈。

栈和堆比较表

参数
基本 内存分配在一个连续的块中。 内存以任意随机顺序分配。
分配和解除分配 由编译器指令自动执行 程序员手动分配
成本 更低 更多
实施 容易 困难
访问时间
主要问题 内存不足 内存碎片
参考地址 优秀 足够
安全 线程安全,存储的数据只能由所有者访问 非线程安全,存储的数据对所有线程可见
灵活性 固定大小 调整是可能的
数据类型结构 线性 分层

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/264240.html

(0)
上一篇 2022年6月7日 01:10
下一篇 2022年6月7日 01:10

相关推荐

发表回复

登录后才能评论