Wrayの知识库 Wrayの知识库
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
首页
  • Java 基础
  • Java 集合
  • Java 并发
  • Java IO
  • JVM
  • Spring Framework
  • Spring Boot
  • Spring Cloud
  • Spring Security
  • MySQL
  • Redis
  • 计算机基础
  • 操作系统原理
  • Linux
  • MacOS
  • Windows
  • 系统工程与研究专题
  • AI 基础
  • 大模型基础
  • Prompt 工程
  • RAG 检索增强生成
  • Agent 智能体
  • AI 应用开发
  • AI 工程化
  • AI 安全与治理
  • AI 面试与设计题
  • 纸质书
  • 电子书
  • 学习课程
疑难杂症
GitHub (opens new window)
  • Java章节编写规范
  • Java基础

  • Java集合

  • Java并发

  • Java IO

  • JVM

    • JVM概述
    • JVM基础

      • JVM体系结构
      • 字节码与Class文件
      • 运行时数据区总览
        • 1. 运行时数据区分类
        • 2. 程序计数器
        • 3. Java 虚拟机栈
        • 4. 本地方法栈
        • 5. Java 堆
        • 6. 方法区
        • 7. 直接内存
        • 8. 阶段小结
        • 9. 内存区域全景图
        • 10. 方法执行时各区域如何配合
        • 11. 开发中常见问题
          • 11.1 堆很空,为什么容器还 OOMKilled
          • 11.2 为什么线程太多会 OOM
          • 11.3 为什么元空间会泄漏
        • 12. Tips 快问快答
      • 栈帧与方法调用
    • 类加载机制

    • 运行时内存

    • 垃圾回收

    • 执行引擎与优化

    • 工具与问题排查

    • 调优实践与面试

目录

运行时数据区总览

运行时数据区是 JVM 执行 Java 程序时使用的内存模型。理解这些区域,是分析 OOM、StackOverflow、GC、线程问题的基础。

# 1. 运行时数据区分类

JVM 运行时数据区可以分为线程私有和线程共享。

线程私有:

  • 程序计数器。
  • Java 虚拟机栈。
  • 本地方法栈。

线程共享:

  • Java 堆。
  • 方法区。
  • 运行时常量池。

此外,JVM 还会使用直接内存等堆外内存。

# 2. 程序计数器

程序计数器记录当前线程正在执行的字节码指令地址。

特点:

  • 线程私有。
  • 占用内存很小。
  • Java 方法执行时记录字节码位置。
  • native 方法执行时值通常未定义。

程序计数器是 JVM 规范中唯一没有规定 OutOfMemoryError 的区域。

# 3. Java 虚拟机栈

每个线程都有自己的 Java 虚拟机栈。每次方法调用都会创建一个栈帧,方法执行结束后栈帧出栈。

栈帧包含:

  • 局部变量表。
  • 操作数栈。
  • 动态链接。
  • 方法返回地址。

常见异常:

  • StackOverflowError:栈深度超过限制。
  • OutOfMemoryError:无法为新线程创建栈或扩展栈。

# 4. 本地方法栈

本地方法栈为 native 方法服务。它和 Java 虚拟机栈类似,只是执行的是本地方法。

例如一些底层 IO、压缩、加密、系统调用可能最终进入 native 代码。

# 5. Java 堆

Java 堆是对象实例和数组的主要分配区域,也是垃圾回收管理的重点。

特点:

  • 线程共享。
  • 可以被分代或分区管理。
  • 大多数对象在堆上分配。
  • 常见异常是 OutOfMemoryError: Java heap space。

# 6. 方法区

方法区存放类相关元数据,例如:

  • 类名。
  • 字段信息。
  • 方法信息。
  • 常量。
  • 静态变量相关数据。
  • JIT 编译后的代码缓存不等同于方法区,但也是 JVM 运行时重要内存。

HotSpot 在 JDK 8 后使用元空间实现方法区,元空间使用本地内存。

# 7. 直接内存

直接内存不是 JVM 规范定义的运行时数据区,但在 NIO、Netty、大文件处理等场景中非常重要。

例如:

ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

直接内存不在 Java 堆中,但仍可能导致:

OutOfMemoryError: Direct buffer memory

# 8. 阶段小结

JVM 内存不是只有堆。堆负责对象,栈负责方法调用,方法区负责类元数据,程序计数器负责线程执行位置,直接内存负责部分堆外数据。排查 JVM 问题时,必须先判断问题发生在哪个区域。

# 9. 内存区域全景图

JVM 进程内存
├─ 线程共享
│  ├─ Java 堆
│  │  ├─ 新生代 / Region
│  │  └─ 老年代 / Old Region
│  ├─ 方法区(HotSpot JDK8+:元空间)
│  └─ 运行时常量池
│
├─ 线程私有
│  ├─ 程序计数器
│  ├─ Java 虚拟机栈
│  │  └─ 栈帧
│  │     ├─ 局部变量表
│  │     ├─ 操作数栈
│  │     ├─ 动态链接
│  │     └─ 返回地址
│  └─ 本地方法栈
│
└─ 堆外/本地内存
   ├─ 直接内存
   ├─ Code Cache
   ├─ GC 内部结构
   ├─ native 库
   └─ mmap 映射

这个图对排障非常有用:不同错误对应不同区域。

错误或现象 优先怀疑区域
Java heap space Java 堆
Metaspace 元空间
StackOverflowError 虚拟机栈
unable to create native thread 线程栈、系统线程限制、本地内存
Direct buffer memory 直接内存
容器 OOMKilled 进程总内存

# 10. 方法执行时各区域如何配合

执行下面代码:

public void service() {
    User user = new User();
    user.setName("Alice");
}

大致过程:

1. service 方法被调用
   -> 当前线程虚拟机栈创建 service 栈帧

2. 执行 new User()
   -> 类元数据在元空间中
   -> User 对象分配在堆上

3. 局部变量 user
   -> 引用保存在栈帧局部变量表
   -> 指向堆中的 User 对象

4. 字符串 "Alice"
   -> 字符串对象在堆上
   -> 字面量相关信息来自常量池

5. 方法结束
   -> service 栈帧出栈
   -> 如果没有其他引用,User 对象变成可回收

这也解释了一个常见问题:局部变量本身在栈中,但它指向的对象通常在堆中。

# 11. 开发中常见问题

# 11.1 堆很空,为什么容器还 OOMKilled

因为容器限制的是进程总内存。即使堆不满,元空间、直接内存、线程栈、Code Cache、native 内存也可能把进程 RSS 推高。

# 11.2 为什么线程太多会 OOM

每个线程都有自己的栈。线程数越多,线程栈总占用越高,还会受到操作系统线程数限制。

# 11.3 为什么元空间会泄漏

元空间保存类元数据。如果类加载器无法回收,它加载过的类元数据也无法释放。热部署、动态代理、脚本编译常见。

# 12. Tips 快问快答

Q:对象一定在堆上吗?

A:语义上对象在堆上,但 JIT 可能通过逃逸分析和标量替换消除实际对象分配。

Q:局部变量在堆上还是栈上?

A:基本类型局部变量值在栈帧局部变量表中;引用类型局部变量的引用在栈上,实际对象通常在堆上。

Q:方法区是不是永久代?

A:方法区是 JVM 规范概念,永久代是 HotSpot JDK 8 之前的实现,JDK 8 之后主要由元空间实现。

Q:程序计数器为什么线程私有?

A:线程切换后需要恢复到原来的字节码执行位置,因此每个线程都要有自己的程序计数器。

上次更新: 2026/06/24, 16:03:01
字节码与Class文件
栈帧与方法调用

← 字节码与Class文件 栈帧与方法调用→

Copyright © 2023-2026 Wray | 鄂ICP备2024050235号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式