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



所有评论(0)