Java内存管理:堆与栈的深度解析

197 阅读2分钟

一、栈内存(Stack Memory):快速、私有的临时存储区

栈内存是一种遵循“后进先出(LIFO) ”原则的线性数据结构。在 JVM 中,它被称为虚拟机栈,主要用于存储方法执行时的临时数据。

  • 存储内容

    • 栈帧:每个方法被调用时,JVM 都会为其创建一个栈帧。栈帧中包含了:

      • 局部变量表:存储基本类型(如 intboolean)和对象引用。
      • 操作数栈:用于方法的运算。
      • 方法返回地址:方法执行完毕后,返回到调用者的地方。
  • 特点

    • 线程私有:每个线程都有自己独立的栈,互不干扰,因此是线程安全的。
    • 生命周期:栈帧的生命周期与方法的执行同步。方法开始时入栈,方法结束时出栈,数据自动销毁。
    • 速度快:栈内存的分配和回收都是静态的、高效的,没有额外的开销。

二、堆内存(Heap Memory):动态、共享的对象仓库

堆内存是 JVM 中最大的一块内存区域,所有通过 new 关键字创建的对象和数组都存储在此。

  • 存储内容

    • 对象实例:包括 StringPersonInteger 等所有类实例。
    • 数组:所有数组的实例。
  • 特点

    • 线程共享:所有线程共享同一块堆内存,因此需要处理线程安全问题。
    • 生命周期:堆中对象的生命周期由**垃圾回收器(GC)**管理。当一个对象不再被任何引用指向时,GC 会在合适的时候回收其内存。
    • 速度慢:堆内存的分配和回收都是动态的,会涉及 GC 过程,因此速度慢于栈内存。

三、堆与栈的协作模式

堆和栈在 Java 程序中协同工作,共同管理数据的生命周期。

  • 基本类型int i = 5。变量 i 及其值 5 都存储在栈中。

  • 对象Cat cat = new Cat("咪咪")

    • new Cat("咪咪")Cat 对象实例存储在堆中
    • cat 变量:cat 变量存储了 Cat 对象在堆中的地址,这个变量本身存储在栈中

四、内存溢出与String的特殊性

  • 栈溢出:当方法调用深度超过栈的容量时,会抛出 StackOverflowError。这通常发生在无限递归过深的方法调用中。

  • 堆溢出:当堆中没有足够的内存来分配新对象时,会抛出 OutOfMemoryError。这通常发生在创建大量对象内存泄漏时。

  • String 的存储

    • String s = "abc":字符串常量 "abc" 存储在字符串常量池中。在 Java 7 之后,字符串常量池位于堆中。
    • String s = new String("abc")new 关键字强制在堆中创建一个新的 String 对象。