JVM - 运行时数据区
JVM 解密 —— 运行时数据区
1. 核心理论:JVM 是如何管理内存的?
当一个 Java 程序运行时,JVM 会在内存中划分出不同的区域来存储各种数据。这些区域统称为运行时数据区 (Runtime Data Areas)。根据《Java虚拟机规范》,它主要分为两大类:线程共享的和线程私有的。
JVM 运行时数据区
├── 线程私有 (每个线程都有一份)
│ ├── 程序计数器 (Program Counter Register)
│ ├── Java 虚拟机栈 (Java Virtual Machine Stack)
│ └── 本地方法栈 (Native Method Stack)
│
└── 线程共享 (所有线程共享一份)
├── Java 堆 (Java Heap)
└── 方法区 (Method Area)
2. 深度剖析:五大内存区域
2.1 线程私有区域
这部分区域的生命周期与线程相同,随线程的创建而创建,随线程的销毁而销毁。
-
程序计数器 (PC Register)
- 作用: 是一块很小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。JVM 通过改变这个计数器的值来选取下一条需要执行的字节码指令。
- 特点: 它是唯一一个在 Java 虚拟机规范中没有规定任何
OutOfMemoryError(堆内存溢出错误)情况的区域。
-
Java 虚拟机栈 (JVM Stack)
- 作用: 存放栈帧 (Stack Frame)。每个方法在执行时,JVM 都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。我们常说的“栈内存”主要就是指这里。
- 局部变量表: 存放了编译期可知的各种基本数据类型(
boolean,byte,char,short,int,float,long,double)、对象引用(reference 类型)。 - 异常: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出
StackOverflowError(栈溢出错误);如果栈可以动态扩展,但在扩展时无法申请到足够的内存,会抛出OutOfMemoryError(堆内存溢出错误)。
-
本地方法栈 (Native Method Stack)
- 作用: 与虚拟机栈非常相似,区别在于它为虚拟机使用到的本地方法 (Native Method) 服务。本地方法通常是由 C 或 C++ 实现的。
2.2 线程共享区域
这部分区域随虚拟机的启动而创建,是所有线程共享的,也是垃圾回收(GC)发生的主要区域。
-
Java 堆 (Java Heap)
- 作用: 是 JVM 所管理的内存中最大的一块。它的唯一目的就是存放对象实例。我们
new出来的几乎所有对象,都在这里分配内存。 - 特点: 是垃圾收集器管理的主要区域,因此也被称作“GC堆”。从内存回收的角度看,现在的收集器基本都采用分代收集算法,所以 Java 堆还可以细分为:新生代和老年代。
- 异常: 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出
OutOfMemoryError(堆内存溢出错误)。
- 作用: 是 JVM 所管理的内存中最大的一块。它的唯一目的就是存放对象实例。我们
-
方法区 (Method Area)
-
作用: 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
-
演进:从永久代 (PermGen) 到元空间 (Metaspace)
- JDK 1.7 及之前: 方法区由永久代实现。永久代是堆的一部分,有固定的内存大小上限(
-XX:MaxPermSize),在动态加载大量类的场景下,非常容易导致OutOfMemoryError(堆内存溢出错误): PermGen space。 - JDK 1.8 及之后: 永久代被彻底移除,取而代之的是元空间。元空间使用的是本地内存 (Native Memory),不再占用堆内存,其大小理论上只受限于机器的总物理内存,从而极大地降低了因类信息过多而导致 OOM 的风险。
- JDK 1.7 及之前: 方法区由永久代实现。永久代是堆的一部分,有固定的内存大小上限(
-
JDK 8 迁移后,数据都去哪儿了?
- 类元信息 (类的结构、方法、字段等): 从永久代迁移到了元空间。
- 字符串常量池: 从永久代迁移到了 Java 堆中。
- 静态变量 (static variables): 从永久代迁移到了 Java 堆中,与该类的
java.lang.Class对象存放在一起。
-
3. 生活中的例子:大学图书馆学习
-
JVM: 整座大学。
-
线程: 每个来上自习的学生。
-
Java 虚拟机栈(线程私有): 每个学生自己桌上的草稿纸。你在算微积分(执行方法A)时,在草稿纸上写满了公式(局部变量);接着你又算物理题(执行方法B),又用了一张新的草稿纸。算完物理题,就把这张草稿纸扔了(方法B出栈)。你的草稿纸别人不能用。 -
程序计数器(线程私有): 你正在阅读的书本上,用来标记当前读到哪一页的书签。你知道接下来该读哪一页。 -
Java 堆(线程共享): 图书馆的中央书库。所有“书籍”(对象实例)都存放在这里。所有学生都可以从这里借阅(引用)书籍。 -
方法区(线程共享): 图书馆的索引区/卡片目录。这里存放着所有书籍的“元信息”(类信息),比如书名、作者、分类号、有多少章节等。所有学生都共享这份目录来查找他们需要的书。

浙公网安备 33010602011771号