JVM 运行时数据区 —— 5 大块内存

一句话

JVM 的内存按用途分为 5 块区域:程序计数器、Java 虚拟机栈、本地方法栈(线程私有)、堆、元空间(线程共享)。每一块都有各自的用途和异常。


5 大区域总览

JVM 运行时数据区 │ ├── 线程私有(每个线程独立一份) │ ├── ① 程序计数器 ← 很小,记录当前线程执行到哪一行 │ ├── ② Java 虚拟机栈 ← 方法调用、局部变量、对象引用 │ └── ③ 本地方法栈 ← native 方法用的栈 │ └── 线程共享(所有线程共用) ├── ④ 堆(Heap) ← 所有对象实例(new 出来的都在这里) └── ⑤ 元空间(Metaspace)← 类结构信息、方法字节码、常量池

线程私有区域

① 程序计数器

作用​:记录当前线程执行到哪一行字节码指令。

publicintadd(inta,intb){intc=a+b;// ← 当前执行到这里,程序计数器就记录这一行returnc;}

为什么需要​?多线程切换时,切回来要知道从哪继续执行。

特点​:JVM 规范里唯一不会 OOM 的区域。很小。

② Java 虚拟机栈

作用​:每个方法调用时分配一个​栈帧​,方法执行完栈帧出栈。

methodA 进入: 栈:[methodA 栈帧] methodA 调用 methodB: 栈:[methodA 栈帧] [methodB 栈帧] ← methodB 在栈顶 methodB 结束: 栈:[methodA 栈帧] ← methodB 出栈 methodA 结束: 栈:[] ← methodA 出栈

栈帧里有什么?

一个栈帧: ├── 局部变量表 ← 方法里的局部变量和对象引用 ├── 操作数栈 ← 计算时的临时"草稿纸" ├── 动态链接 ← 指向运行时常量池的引用 └── 返回地址 ← 方法结束后回到哪里

关键理解​:对象在堆里,栈里只存对象的引用地址(“遥控器”)。

Useruser=newUser();// 栈里: [user — 地址 0x1234] ← 遥控器// 堆里: [User 对象] ← 房子

异常​:递归太深 →StackOverflowError

③ 本地方法栈

和虚拟机栈功能一样,但给native 方法使用的。

// native 方法 —— 用 C/C++ 实现的 Java 方法publicnativeinthashCode();// Object 类,底层 C++ 实现publicstaticnativevoidsleep(longmillis);// Thread 类

Java 方法调用用虚拟机栈,native 方法调用用本地方法栈——两者分开管理,互不干扰。


线程共享区域

④ 堆(Heap)

所有对象实例都放在堆里。

newUser();// → 堆newArrayList<>();// → 堆newint[10];// → 堆

堆内部按"对象的存活时间"分为两代:

堆 ├── 新生代(朝生夕死的对象) │ ├── Eden(80%) ← 所有新对象先放这里 │ └── Survivor │ ├── S0(10%) ← From │ └── S1(10%) ← To │ └── 老年代(熬过多次 GC 的对象)

堆是最容易出问题的区域​——OOM 绝大多数就是堆满了。具体分代和对象流转见下一篇。

⑤ 元空间(Metaspace)

作用​:存放类的结构信息——可以理解为"对象的图纸"。

堆里: [User 对象1] [User 对象2] ... [User 对象10000] ← 10000 份实例 元空间:[User 类的结构信息] ← 只有 1 份图纸

元空间里存的是:

  • 类的全限定名(com.example.User
  • 类的字段描述(id: Long,name: String
  • 类的方法字节码
  • 常量池

JDK7 vs JDK8 的变化​:

JDK7(方法区/永久代)JDK8(元空间)
位置在堆里不在堆里,用本地内存
大小限制有上限,容易永久代 OOM默认不限制(除非设置 MaxMetaspaceSize)
字符串常量池在方法区移到了堆里(避免 OOM)
静态变量在方法区移到了堆里

面试问答

一个对象在哪个区域?

代码JDK8 存在哪
new User()(对象实例)
User.class(Class 对象)
类的字段/方法/字节码(“图纸”)元空间
"hello"(字符串常量)​(JDK8 字符串常量池在堆里)
static int count(静态变量)​(JDK8 移到了堆里)

栈和堆的区别?

栈(虚拟机栈):存方法调用和局部变量,"控制" 堆:存对象实例,"数据" 栈里的引用指向堆里的对象

参考来源

  • 《深入理解 Java 虚拟机》第 2 章
  • JDK8 JVM 规范