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 面试中的经典考点。


Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐