外观
一句话答案
JVM 内存分堆(新生代/老年代)、方法区(元空间)、虚拟机栈、本地方法栈、程序计数器,其中堆和方法区线程共享。
核心要点
JVM 运行时数据区(JDK 8):
┌─────────────────────────────────────────────────────────┐
│ JVM 运行时数据区 │
│ │
│ ┌──────────────────────────────┐ ← 线程共享 │
│ │ 堆(Heap) │ │
│ │ Eden | S0 | S1 | 老年代 │ │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────────────────────────┐ ← 线程共享 │
│ │ 方法区(Metaspace) │ │
│ │ 类信息 | 常量池 | 静态变量 │ (JDK8 在本地内存) │
│ └──────────────────────────────┘ │
│ │
│ ┌──────────┐ ← 线程私有 │
│ │ 虚拟机栈 │ 局部变量表、操作数栈、栈帧 │
│ └──────────┘ │
│ ┌──────────┐ ← 线程私有 │
│ │ 本地方法栈 │ Native 方法的栈帧 │
│ └──────────┘ │
│ ┌──────────┐ ← 线程私有 │
│ │ 程序计数器 │ 当前线程执行的字节码行号 │
│ └──────────┘ │
└─────────────────────────────────────────────────────────┘各区域详解:
1. 堆(Heap)—— 最大的一块,GC 主战场
- 存放:所有对象实例、数组(
new出来的东西都在这) - 线程共享,GC 主要回收此区域
- 划分:年轻代(Eden + S0 + S1) + 老年代
2. 方法区(Method Area)—— JDK 8 改为 Metaspace
- 存放:类的元信息(字段、方法、接口描述)、运行时常量池、静态变量、JIT 编译后的代码缓存
- JDK 7 之前叫永久代(PermGen,堆内),JDK 8 改为 Metaspace(本地内存,不受
-Xmx限制) String.intern()的字符串字面量从 JDK 7 起移入堆中
3. 虚拟机栈(VM Stack)
- 每个线程私有,随线程创建
- 由栈帧(Stack Frame)组成,每调用一个方法压入一个栈帧
- 栈帧包含:局部变量表、操作数栈、动态链接、方法返回地址
4. 本地方法栈(Native Method Stack)
- 专门服务
native方法(JNI) - HotSpot 将虚拟机栈和本地方法栈合并为一个
5. 程序计数器(Program Counter Register)
- 最小的一块内存,记录当前线程执行的字节码指令地址
- 唯一一个不会 OOM 的区域
- 线程切换后用于恢复执行位置
追问与易错
追问方向:
- 方法区和元空间什么关系?(JDK8 用元空间实现方法区,存在本地内存而非堆中)
- 虚拟机栈中存什么?(栈帧:局部变量表/操作数栈/动态链接/方法返回地址)
- 哪些区域会 OOM?(堆/元空间/栈/直接内存都可能)
易错点:
- ❌ "JDK8 没有方法区了"——方法区是规范概念仍存在,只是实现从永久代改为元空间
- ❌ "栈不会溢出"——递归过深会 StackOverflowError
💡 记忆锚点
JVM内存像一栋楼:堆是公共大厅(线程共享,放对象),方法区/Metaspace是档案室(类信息),虚拟机栈是每人的办公桌(线程私有,栈帧),程序计数器是书签(记执行位置,唯一不OOM的区域)。JDK8把档案室从楼内搬到楼外(永久代变元空间,用本地内存)。