Java内存模型详解:栈、堆、方法区、本地方法栈与程序计数器
在 Java 面试中,JVM 内存模型几乎是必问内容。
常见问题:
- 对象存放在哪里?
- new出来的对象为什么在堆中?
- 局部变量为什么在线程栈中?
- 方法区存放什么内容?
- 程序计数器有什么作用?
- 栈溢出和堆溢出的区别是什么?
本文结合实际开发和面试场景,系统讲解 JVM 运行时数据区。
目录
- JVM运行时内存结构
- 程序计数器(Program Counter Register)
- Java虚拟机栈(Java Virtual Machine Stack)
- 本地方法栈(Native Method Stack)
- 堆(Heap)
- 方法区(Method Area)
- JDK6、JDK7、JDK8区别
- 对象创建过程
- 成员变量与局部变量存储位置
- 栈溢出与堆溢出
- 常见面试题
- 总结
一、JVM运行时内存结构
Java程序运行时,JVM会将内存划分为多个区域。
主要包括:
text
┌─────────────────┐
│ 程序计数器 │
├─────────────────┤
│ Java虚拟机栈 │
├─────────────────┤
│ 本地方法栈 │
├─────────────────┤
│ 堆 Heap │
├─────────────────┤
│ 方法区 MethodArea│
└─────────────────┘
可以简单记忆为:
text
线程私有:
程序计数器
虚拟机栈
本地方法栈
线程共享:
堆
方法区
二、程序计数器(Program Counter Register)
程序计数器:
当前线程所执行字节码的行号指示器。
可以理解为:
text
程序执行到哪一行了
记录器。
例如:
java
public static void main(String[] args) {
int a = 10;
int b = 20;
int c = a + b;
}
执行过程:
text
执行第一行
↓
记录位置
↓
执行下一行
↓
记录位置
作用:
text
保证线程切换后
能够恢复到正确执行位置
特点:
text
线程私有
因为:
text
每个线程执行位置都不同
面试高频:
text
唯一不会发生OOM的内存区域
三、Java虚拟机栈(Java Virtual Machine Stack)
Java虚拟机栈简称:
text
栈(Stack)
作用:
text
运行Java方法
每调用一个方法:
java
test();
都会创建:
text
栈帧(Stack Frame)
例如:
java
public void methodA() {
methodB();
}
执行过程:
text
methodA入栈
↓
methodB入栈
↓
methodB执行结束出栈
↓
methodA出栈
图示:
text
栈顶
methodB
methodA
main
栈底
四、局部变量存放在哪里
局部变量:
java
public void test() {
int age = 18;
String name = "Tom";
}
存储位置:
text
栈帧中的局部变量表
因此:
text
局部变量跟随方法
方法结束:
text
立即销毁
这也是你笔记中的:
text
局部变量在栈中
跟着方法走
的来源。
五、本地方法栈(Native Method Stack)
专门用于:
text
Native方法
执行。
例如:
java
public native void start0();
Thread源码:
java
private native void start0();
native表示:
text
不是Java实现
而是:
text
C
C++
操作系统代码
实现。
作用:
text
扩展Java能力
例如:
text
文件系统
硬件交互
网络驱动
操作系统调用
这些很多都依赖本地方法。
六、堆(Heap)
堆是 JVM 最大的一块内存区域。
作用:
text
存放对象
和:
text
数组
例如:
java
Student stu =
new Student();
执行:
java
new Student()
时:
text
对象进入堆
引用变量:
java
stu
存放在栈中。
图示:
text
栈
stu
│
▼
堆
Student对象
七、数组为什么在堆中
例如:
java
int[] nums =
new int[10];
执行:
java
new int[10]
时:
text
数组对象创建在堆中
引用:
java
nums
位于栈中。
图示:
text
栈
nums
│
▼
堆
数组对象
这也是你笔记中的:
text
数组属于引用数据类型
原因。
八、堆中的默认值
对象创建后:
java
new User();
成员变量自动赋默认值。
例如:
java
class User {
int age;
boolean flag;
String name;
}
默认值:
text
int → 0
long → 0L
float → 0.0
double → 0.0
char → '\u0000'
boolean → false
引用类型 → null
原因:
text
堆内存初始化
时自动赋值。
九、方法区(Method Area)
方法区:
存储类信息、方法信息、常量、静态变量等数据。
例如:
java
public class User {
private String name;
public void save() {
}
}
类加载后:
text
User类信息
save方法信息
字段信息
进入方法区。
因此:
text
代码运行前
先加载到方法区
这与你笔记中的:
text
代码预备区
概念类似。
十、静态变量存放在哪里
例如:
java
public class User {
static int count = 0;
}
JDK6:
text
方法区(永久代)
JDK7以后:
text
堆中
JDK8:
text
元空间(Metaspace)
类信息在元空间
静态变量在堆中
因此你的笔记:
text
静态变量在堆中
对于现代JDK是正确的。
十一、对象创建过程
代码:
java
User user =
new User();
执行过程:
第一步
检查类是否加载。
text
User.class
第二步
在堆中分配内存。
text
创建对象
第三步
成员变量赋默认值。
例如:
text
0
false
null
第四步
执行构造方法。
java
public User() {
}
第五步
返回对象地址。
最终:
text
user
↓
对象地址
十二、成员变量与局部变量区别
成员变量
定义:
java
class User {
int age;
}
特点:
text
定义在类中
属于对象
存放于堆
生命周期:
text
对象创建开始
对象销毁结束
局部变量
定义:
java
public void test() {
int age = 18;
}
特点:
text
定义在方法中
存放于栈
生命周期:
text
方法调用开始
方法结束销毁
十三、栈溢出(StackOverflowError)
示例:
java
public void test() {
test();
}
执行:
text
不断调用自己
不断入栈
最终:
text
栈空间耗尽
异常:
text
StackOverflowError
最典型场景:
text
递归没有结束条件
十四、堆溢出(OutOfMemoryError)
示例:
java
List<Object> list =
new ArrayList<>();
while (true) {
list.add(new Object());
}
结果:
text
对象无限增长
最终:
text
Java heap space
异常:
text
OutOfMemoryError
十五、线程私有与线程共享
| 区域 | 是否共享 |
|---|---|
| 程序计数器 | 否 |
| 虚拟机栈 | 否 |
| 本地方法栈 | 否 |
| 堆 | 是 |
| 方法区 | 是 |
记忆口诀:
text
三私有
两共享
十六、面试高频问题
面试题1
对象存放在哪里?
答案:
text
堆中
面试题2
局部变量存放在哪里?
答案:
text
虚拟机栈
面试题3
成员变量存放在哪里?
答案:
text
对象中
对象位于堆
面试题4
方法信息存放在哪里?
答案:
text
方法区
面试题5
StackOverflowError原因?
答案:
text
栈空间耗尽
面试题6
OutOfMemoryError原因?
答案:
text
堆空间耗尽
面试题7
哪个区域不会发生OOM?
答案:
text
程序计数器
十七、JVM内存结构速记图
text
线程私有
程序计数器
虚拟机栈
本地方法栈
────────────
线程共享
堆
方法区
对象:
text
堆
数组:
text
堆
局部变量:
text
栈
类信息:
text
方法区
总结
JVM运行时内存主要包括:
text
程序计数器
虚拟机栈
本地方法栈
堆
方法区
其中:
text
对象、数组 → 堆
局部变量 → 栈
类信息、方法信息 → 方法区
需要牢记:
text
栈跟着方法走
堆跟着对象走
以及:
text
程序计数器是唯一不会发生OOM的区域
这是 JVM 面试中的经典考点。