当前位置:网站首页 > Java基础 > 正文

java 并发基础之内存模型



Java 的内存结构(Memory Structure)是 Java 虚拟机(JVM)在运行时管理内存的方式,它直接关系到 Java 程序的性能和运行的稳定性。

Java 的内存结构可以总结为以下几个关键部分:

  • 堆内存:存储对象和数组,是垃圾回收的主要目标。
  • 栈内存:存储局部变量、方法调用栈帧,线程私有。
  • 方法区:存储类的元数据、常量池、静态变量等,线程共享。
  • 程序计数器:记录当前线程执行的位置。
  • 本地方法栈:存储本地方法调用信息。
  • 直接内存:操作系统本地内存,通过 NIO 进行快速数据传输。

1. 堆内存(Heap Memory)

堆内存是 Java 内存结构中最大的一部分,主要用于存储所有的对象实例和数组。在 Java 程序运行时,所有对象的创建都是在堆中分配内存。堆内存也是垃圾回收(Garbage Collection,GC)的主要工作区域。

堆内存可以进一步分为两个主要区域:

  • 新生代(Young Generation)
    • 新生代又分为三个部分: 和两个相同大小的 (一般命名为 Survivor 0 和 Survivor 1 或 From 和 To)。
    • 新生代存放的是新创建的对象,大部分对象在这里很快就会被标记为垃圾并被清理(因为大多数对象生命周期很短)。当对象经过多次 GC 后没有被回收,会从 Eden 区和 Survivor 区晋升到老年代。
  • 老年代(Old Generation)
    • 老年代存储的是生命周期较长的对象,这些对象是从新生代中经过多次垃圾回收存活下来的。垃圾回收在老年代发生频率较低,但一旦发生,通常会是一个 “Major GC” 或 “Full GC”。

2. 栈内存(Stack Memory)

每个线程在 JVM 中都有自己的栈,栈内存用于存储局部变量、方法调用和线程信息。每当一个方法被调用时,都会为该方法创建一个栈帧(Stack Frame),这个栈帧包含了该方法的局部变量表、操作数栈和返回地址等信息。

  • 栈中的变量存储的是基本数据类型(如 、)的值,以及对象的引用,而对象本身存储在堆中。
  • 当方法调用完成后,栈帧将会出栈,内存将被释放。

栈内存的特点:

  • 线程私有:每个线程都有自己的栈,栈内存不共享。
  • 自动管理:栈的大小是有限的,如果栈空间不足,会抛出 。

3. 方法区(Method Area)

方法区(Method Area)是 JVM 内存的一部分,它存储了每个类的结构信息,如类的元数据、常量池、静态变量和类构造方法(即类的字节码)。方法区在 JVM 启动时被创建,是所有线程共享的。

  • 类信息:当一个类第一次被加载时,它的类信息会被存储在方法区中。包括类名、父类名、修饰符、字段、方法和接口等。
  • 常量池:常量池存储的是编译期生成的一些常量(如字符串常量、数字常量等),以及方法和字段的引用。
  • 静态变量:方法区还保存类的静态变量和方法。

注意:在早期的 Java 实现中,方法区被称为永久代(Permanent Generation,PermGen),从 Java 8 开始,永久代被移除,取而代之的是元空间(Metaspace)。元空间位于本地内存而非堆内存中,解决了以前永久代中的内存限制问题。

4. 程序计数器(Program Counter, PC Register)

程序计数器是每个线程私有的,存储的是当前线程执行的字节码指令的地址。JVM 在多线程环境下,通过程序计数器来保存每个线程的执行状态(即每个线程执行到哪一条指令)。当线程切换时,程序计数器可以保证线程恢复执行时能够从正确的位置开始。

  • 如果当前线程执行的是 Java 方法,程序计数器存储的是正在执行的字节码指令地址。
  • 如果执行的是本地方法(Native Method),则程序计数器为空(Undefined)。

5. 本地方法栈(Native Method Stack)

本地方法栈用于存储本地方法调用的信息。当 Java 调用非 Java 语言编写的方法时,如 C 或 C++ 编写的代码,相关的信息会存储在本地方法栈中。

  • 与 Java 栈类似,本地方法栈也是线程私有的。
  • 如果调用的本地方法使用了 JNI(Java Native Interface),它的调用信息会被保存到本地方法栈中。

6. 直接内存(Direct Memory)

直接内存并不是 JVM 内存结构的严格一部分,但它经常被 JVM 使用。直接内存使用的是操作系统的本地内存,而不是堆内存。通过 ,Java 程序可以通过直接内存进行快速的数据传输,避免在堆内存和操作系统内存之间进行频繁的数据复制。

直接内存的大小并不受 JVM 堆内存大小的限制,而是受限于操作系统的总内存。

7. 垃圾回收机制(Garbage Collection, GC)

Java 的垃圾回收机制是 JVM 自动管理内存的重要部分,它负责清理不再使用的对象,释放堆内存空间。垃圾回收主要作用在堆内存上,GC 的触发和算法直接影响程序的性能。

常见的垃圾回收算法包括:

  • 标记-清除(Mark-Sweep):标记所有存活的对象,随后清理未标记的对象。
  • 标记-整理(Mark-Compact):标记存活对象后,通过整理移动对象来消除碎片化。
  • 复制算法(Copying):将存活的对象从一块内存复制到另一块内存,清理掉不再存活的对象。

下面以几个常见的场景为例,分别讲解不同内存区域的作用和内存的管理机制。

场景 1:基本数据类型与局部变量的分配(栈内存)

 
内存使用流程:
  1. 栈内存的分配
    • 当 方法开始执行时,JVM 会为该方法在栈内存中分配一个栈帧(Stack Frame),该栈帧用来存储方法的局部变量表、操作数栈和返回地址。
    • 在局部变量表中,、 和 三个变量都是 类型,属于基本数据类型,它们的值分别存储在栈内存中,而不是堆内存。
  2. 方法执行
    • JVM 在栈帧中执行 ,将 10 存储在局部变量 的位置。
    • 同理, 也存储在栈中。
    • 计算 后, 的值为 30,存储在栈帧中。
  3. 方法结束
    • 当 方法执行完成时,栈帧被移除,分配给 方法的内存也被释放。这些局部变量随着方法的结束而消失。

场景 2:对象的创建与垃圾回收(堆内存)

 
内存使用流程:
  1. 对象创建(堆内存分配)
    • 在 方法开始时,JVM 在栈中为 方法创建一个栈帧,存储局部变量 和 。
    • 当执行 时:
      • 关键字表示需要在堆内存中创建一个新的 对象。
      • JVM 在堆内存中分配空间,存储 对象的实例(包括 字段)。
      • 存储在栈内存中,它只是对堆中 对象的引用,指向堆中分配的 对象。
      • 同样地, 是对 对象的引用。
  2. 对象的生命周期和垃圾回收
    • 当 调用 类的 方法时,JVM 会通过 的引用找到堆内存中的 对象,执行方法。
    • 方法结束后, 和 仍然在栈中存活,但一旦 方法结束,这些对象的引用会失效。
    • 垃圾回收机制(GC)将会负责回收堆中不再被引用的 对象(即当没有任何栈帧中指向这些对象时,它们将被标记为垃圾,并在适当的时候回收内存)。

场景 3:静态变量与方法区的使用

 
内存使用流程:
  1. 静态变量的存储(方法区/元空间)
    • 类的 是一个 变量,它的内存分配不同于实例变量。静态变量存储在方法区(在 Java 8 之后是元空间),该区域是所有线程共享的,而不是属于某个对象的实例。
    • 当 JVM 加载 类时, 变量被分配在方法区,并初始化为 0。
  2. 方法执行
    • 每次调用 ,静态方法在栈中被调用(创建栈帧), 变量被增加,但它始终位于方法区,而不是堆中或栈中。
    • 静态变量的生命周期与类的生命周期一致,类在程序执行过程中只加载一次, 也只分配一次,无论创建多少个 实例, 的值都不会改变位置。

场景 4:字符串常量池与方法区

 
内存使用流程:
  1. 字符串常量池
    • 将字符串 分配到方法区的字符串常量池中。Java 会在加载类时检查常量池中是否已经存在该字符串,如果存在,直接返回引用;如果不存在,则在常量池中创建该字符串。
    • 当 执行时,JVM 检查常量池,发现 java 并发基础之内存模型 已经存在,因此 和 都引用同一个常量池中的字符串对象。
  2. 堆内存中的字符串对象
    • 这一行创建了一个新的字符串对象,它被分配到堆内存中,因此 引用的是堆中的对象,而不是常量池中的字符串。
  3. 比较结果
    • 返回 ,因为它们引用的是同一个常量池中的字符串。
    • 返回 ,因为 引用的是堆中的对象,而不是常量池中的那个对象。

垃圾回收的触发与内存管理

垃圾回收(Garbage Collection,GC)是 Java 内存管理中的重要机制,它负责清理那些不再被引用的对象,从而释放堆内存。常见的垃圾回收机制包括:

  • 新生代 GC(Minor GC):主要作用于新生代(Eden 和 Survivor 区域),处理短生命周期的对象。
  • 老年代 GC(Major GC 或 Full GC):主要作用于老年代,回收生命周期较长的对象。
  • GC 的触发条件:当堆内存不足,特别是 Eden 区满时,GC 会被触发来回收无用的对象。

通过合理的垃圾回收策略,JVM 保证了堆内存的有效利用,避免内存泄漏和程序崩溃。

总结

版权声明


相关文章:

  • java游戏服务器基础2024-11-17 11:10:06
  • java基础类和方法2024-11-17 11:10:06
  • java如何夯实基础2024-11-17 11:10:06
  • 233java基础教学2024-11-17 11:10:06
  • java中引用类型与基础类型2024-11-17 11:10:06
  • java基础案例教程推荐吗2024-11-17 11:10:06
  • java基础集合框架增删改查2024-11-17 11:10:06
  • java零基础案例2024-11-17 11:10:06
  • java基础知识必问2024-11-17 11:10:06
  • java基础教程目录2024-11-17 11:10:06