源本科技 | 码上会

Java 内存管理

2026/01/23
13
0

学习目标

  • 理解 JVM 的内存区域划分及其用途

  • 掌握堆(Heap)、栈(Stack)、方法区(Method Area)等核心内存区域的特点

  • 能够区分不同类型的变量(静态、实例、局部)在内存中的存储位置

  • 了解垃圾回收机制与线程安全在内存管理中的体现


JVM 内存结构

Java 虚拟机(JVM)在运行时将内存划分为多个运行时数据区(Runtime Data Areas)。这些区域可分为两类:

  • 线程共享区域:整个 JVM 进程共用(如堆、方法区)

  • 线程私有区域:每个线程独立拥有(如虚拟机栈、本地方法栈、程序计数器)

这种设计既支持高效的多线程并发,又保障了内存使用的安全性。


1. 堆

核心特点

  • 唯一性:每个 JVM 进程有且仅有一个堆

  • 共享性:所有线程共享堆内存,访问需同步

  • 用途:存放所有通过 new 创建的对象实例数组

  • 生命周期:从 JVM 启动创建,到 JVM 退出销毁

  • 垃圾回收:堆是 GC(Garbage Collection)的主要战场,必须进行垃圾回收

示例说明

Scanner sc = new Scanner(System.in);
  • new Scanner(...) 创建的对象实例 → 存储在

  • 引用变量 sc → 存储在 (指向堆中的对象)

开发者可通过 -Xms-Xmx 参数调整堆的初始大小和最大大小。


2. 方法区

核心特点

  • 逻辑归属:规范中定义为堆的一部分,但现代 JVM(如 HotSpot)通常将其独立实现

  • HotSpot 实现:使用 Metaspace(元空间),位于本地内存(Native Memory),而非 Java 堆

  • 用途:存储类级别的元数据,包括:

    • 类结构(字段、方法信息)

    • 方法字节码

    • 静态变量(static 字段)

    • 常量池(Constant Pool)

    • 接口与注解信息

  • 垃圾回收不强制,取决于 JVM 实现。Metaspace 在类卸载时可能回收

变量存储示例

class Geeks {
    static int v = 100;   // → 存储在方法区(Metaspace)
    int i = 10;           // → 实例变量,随对象存于堆
}

注意:static 变量属于类,而非对象,因此不随对象分配在堆中。


3. JVM 虚拟机栈

核心特点

  • 线程私有:每个线程启动时创建独立的栈

  • 用途:存储方法执行的上下文,包括:

    • 局部变量(Local Variables)

    • 方法参数

    • 操作数栈

    • 动态链接

    • 返回地址

  • 结构:由多个栈帧(Stack Frame)组成,每个方法调用对应一个栈帧

  • 自动管理:方法执行完毕后,其栈帧自动出栈并释放

  • 线程安全:因栈私有,无需同步

示例

public void display() {
    int s = 20; // → 局部变量,存储在当前方法的栈帧中
}

栈内存分配和释放速度极快,遵循 LIFO(后进先出)原则。


4. 本地方法栈

  • 用于支持 native 方法(非 Java 编写的代码,如 C/C++)

  • 每个线程也有独立的本地方法栈

  • 具体实现由 JVM 厂商决定,可能与 JVM 栈合并或分离


5. 程序计数器

  • 线程私有:每个线程都有自己的 PC 寄存器

  • 作用

    • 对于 Java 方法:记录当前正在执行的字节码指令地址

    • 对于 native 方法:值未定义

  • 唯一不会发生 OOM 的区域:所需内存极小且固定


堆 vs 栈

特性

堆(Heap)

栈(Stack)

存储内容

对象实例、数组

局部变量、方法调用帧

内存大小

较大(可配置)

较小(默认几百 KB 到几 MB)

访问速度

相对较慢

极快

分配方式

手动分配(new),自动回收

自动分配与释放

生命周期

对象存活期间

方法执行期间

线程共享

是(需同步)

否(线程安全)

内存布局

随机访问

LIFO(后进先出)


垃圾回收(GC)与内存管理

  • 堆是 GC 的主要区域:JVM 自动识别并回收不再被引用的对象

  • 方法区 GC 不强制:主要回收废弃常量和无用类(需满足特定条件)

  • 栈和 PC 寄存器无需 GC:随方法 / 线程结束自动释放

Java 的自动内存管理极大降低了内存泄漏和悬空指针风险,但开发者仍需注意:

  • 避免长生命周期对象持有短生命周期对象的引用(如静态集合缓存)

  • 合理使用弱引用(WeakReference)等工具


思考题

  1. 为什么 static 变量存储在方法区而不是堆?如果一个类被多次加载(如不同类加载器),会有多个 static 变量副本吗?

  2. 在高并发应用中,大量线程是否会导致栈内存耗尽?如何通过 JVM 参数优化?

  3. Metaspace 与旧版 PermGen(永久代)相比有哪些优势?为什么 HotSpot 要将其移出 Java 堆?