JVM 内存与方法执行 | AI生成和翻译

Home 2026.03

问题: JVM 内存模型是如何工作的,它如何管理对象和方法调用?

回答:


概述

JVM 将其内存分为几个逻辑运行时数据区域,每个区域在程序执行期间扮演特定角色。这种结构化的内存模型确保不同类型数据之间的隔离,支持多线程,并启用如自动垃圾回收等功能。


JVM 内存区域

1. Heap

Java Heap 是动态内存分配的主要区域。程序执行期间创建的所有类实例、数组和对象都存储在这里。与限于单个线程的 stack 内存不同,heap 是 JVM 中所有线程可访问的共享内存空间。

Heap 分为两个主要区域:Young GenerationOld Generation。Young Generation 进一步分为 Eden Space(新对象首先创建的地方)和两个 Survivor Spaces(S0 和 S1),幸存垃圾回收的对象会被移动到这里。

2. Stack(每个线程)

JVM 中运行的每个线程都有自己的 thread stack。基本类型的本地变量完全存储在 thread stack 上,对其他线程不可见。即使两个线程执行相同的代码,它们也会在其各自的 thread stack 中创建本地变量的独立副本。

Stack 内存随着新方法的调用和返回而增长和收缩。当方法被调用时,会创建一个新的 stack frame;当方法返回时,对应的 stack frame 被移除。在 stack 中声明的变量仅在创建它们的 메서드运行期间存在。

3. Method Area / Metaspace

Method Area 是 Java heap 的一部分,由所有线程共享。它存储类级信息,如类元数据、静态变量和常量池(存储如字符串字面量和引用的常量)。

在 Java 8+ 中,Metaspace 取代了旧的 PermGen。与 PermGen 的固定大小不同,Metaspace 可以动态增长,仅受可用 native memory 的限制。这种灵活性避免了令人畏惧的 OutOfMemoryError: PermGen space,但如果发生类加载器泄漏或动态类生成导致的失控增长,则会引入风险。

4. PC(Program Counter)寄存器

每个 JVM 线程都有一个 Program Counter (PC) 寄存器。对于 non-native 方法,它存储当前正在执行的 JVM 指令地址。

5. Native Method Stack

Native method stack 处理与 Java 代码交互的 native 方法的执行。此内存在每个线程创建时为其分配,可以具有固定或动态大小。


方法调用工作原理(Stack Frames)

每次调用方法时,JVM 都会在 thread 的 stack 上分配一个新的 stack frame。此 frame 是一个自包含的内存单元,持有执行该方法所需的所有数据。Frame 在 stack 上保留,直到方法正常完成或异常退出(例如由于异常)。方法返回后,其 frame 从 stack 中移除,内存会自动回收——stack 内存无需垃圾回收。

每个 stack frame 包含:

实际示例——调用 m1 → m2 → m3: 当 m1 调用 m2 时,一个新 frame 被推到 m1 的 frame 之上。当 m2 调用 m3 时,另一个 frame 被推到顶部。m3 完成后,其 frame 被弹出,控制权返回 m2。然后 m2 和 m1 也同样处理。

如果 stack 超过其分配空间(例如由于无限递归),则会发生 StackOverflowError


对象管理原理(Heap 和 Garbage Collection)

对象生命周期

新对象在 Heap 的 Young Generation(Eden Space)中分配。当 Young Generation 填满时,会发生 minor garbage collection。长期存活的对象会被移动到 Old(Tenured)Generation。

经多次 Minor GC 存活的对象最终会被提升到 Old Generation。这种“代际假设”——大多数对象很快就会死亡——驱动所有现代 JVM 的垃圾收集器设计。

Garbage Collection —— 工作原理

核心原则是识别应用不再“reachable”的对象。GC roots 包括:当前线程调用栈中的对象、类的静态变量以及用于同步的对象。垃圾收集器从这些 roots 开始,遍历整个对象引用图。它能到达的任何对象被视为“live”;无法到达的对象则是垃圾。

大多数现代 GC 使用 Mark-and-Sweep 算法: Mark Phase:垃圾收集器从 GC roots 开始遍历对象图,并标记遇到的每个 live 对象。Sweep Phase:标记完成后,收集器扫描整个 heap;任何未标记的对象现在被视为 unreachable,其内存被回收。

GC 事件类型

现代 GC 收集器

不同的收集器提供不同的权衡:Serial GC 用于单线程环境;Parallel GC 使用多个线程进行 GC;CMS (Concurrent Mark-Sweep) 用于低暂停时间;G1 (Garbage First) 平衡延迟和吞吐量。


总结:各项存放位置

数据 内存区域
本地变量(基本类型) Stack
对象引用 Stack(引用) + Heap(实际对象)
新对象 / 数组 Heap —— Young Generation(Eden)
长寿对象 Heap —— Old Generation
静态变量 Metaspace / Method Area
类元数据 Metaspace
方法字节码 Metaspace / Code Cache
当前指令指针 PC Register
Native 方法执行 Native Method Stack

并发注意事项(Java Memory Model / JMM)

JMM 定义了一个线程所做的更改何时对其他线程可见。由于 CPU caches,一个线程对变量的更新可能不会立即对另一个线程可见。JVM 和处理器也可以重新排序指令以优化性能——如果不正确管理,这种重新排序可能导致意外行为。

确保线程安全的关键工具:


参考资料:


Back Donate