2. JVM 内存模型回顾:堆、栈、方法区(Metaspace)的基本概念与关系
在深入分析 Metaspace GC 停顿之前,必须重新建立对 JVM 内存模型的精确认知。数据库迁移升级后,类加载器数量、动态生成的类元数据、字符串常量池的膨胀,都会直接冲击 Metaspace 区域,进而引发频繁的 Full GC 甚至 Metaspace OOM。本节从堆、栈、方法区三个核心区域出发,厘清各自职责与交互关系。
2.1 三大核心区域概述
JVM 运行时数据区按照线程共享与线程私有分为两大类:
- 线程私有:程序计数器、虚拟机栈、本地方法栈。生命周期与线程绑定,无需 GC 管理。
- 线程共享:堆(Heap)、方法区(Method Area,JDK 8+ 实现为 Metaspace)。是 GC 和内存调优的主战场。
| 区域 | 线程归属 | 存储内容 | GC 行为 | 常见异常 |
|---|---|---|---|---|
| 堆(Heap) | 共享 | 对象实例、数组 | Young GC / Full GC | OutOfMemoryError: Java heap space |
| 虚拟机栈(Stack) | 私有 | 局部变量表、操作数栈、动态链接、方法出口 | 无(自动回收) | StackOverflowError / OutOfMemoryError |
| 方法区(Metaspace) | 共享 | 类元数据、方法字节码、常量池、静态变量、JIT 代码 | Full GC(Metaspace GC) | OutOfMemoryError: Metaspace |
升级关注点: 数据库驱动升级、ORM 框架版本变更、连接池类加载隔离,都会导致 Metaspace 中类元数据激增。堆内存充足但 Metaspace 频繁 GC 是升级后停顿的典型特征。
2.2 堆(Heap)—— 对象生命周期的主战场
堆是 JVM 管理的最大一块内存,几乎所有对象实例和数组都在这里分配。堆在物理上不连续,逻辑上连续,且大小可通过 -Xms 和 -Xmx 控制。
堆内部按分代设计(除 G1 等分区收集器外):
- 新生代(Young Generation):Eden + Survivor0 + Survivor1。对象优先在 Eden 分配,Minor GC 后存活对象在 Survivor 区交换。
- 老年代(Old Generation):长期存活的对象(默认 15 次 Minor GC 后晋升),以及大对象直接进入老年代。
- 元空间(Metaspace):JDK 8 起替代永久代,存放类元数据,属于堆外本地内存。
关键指标: 升级后若老年代占用率持续高于 70% 且 Metaspace 使用率同步攀升,说明类加载泄漏或元数据膨胀正在触发 Full GC。
2.3 虚拟机栈(Stack)—— 线程执行的基石
每个线程创建时都会分配一个独立的虚拟机栈,生命周期与线程一致。栈由一个个栈帧(Stack Frame)组成,每个方法调用对应一个栈帧入栈,方法返回则栈帧出栈。
栈帧内部结构:
- 局部变量表:存放方法参数和局部变量,以 Slot 为单位。
- 操作数栈:字节码指令的运算工作区。
- 动态链接:指向运行时常量池中该方法的符号引用。
- 方法出口:正常或异常返回时恢复调用者状态。
栈深度由 -Xss 控制。数据库迁移升级后,若连接池线程数增加或递归调用深度异常,可能触发 StackOverflowError。
2.4 方法区(Metaspace)—— 类元数据的归宿
方法区在 JDK 8 之前由永久代(PermGen)实现,JDK 8 之后被 Metaspace 取代。Metaspace 使用本地内存(Native Memory),默认上限仅受操作系统限制,但可通过 -XX:MaxMetaspaceSize 设置上限。
Metaspace 存储内容:
- 类的结构信息(字段、方法、接口、注解等)
- 运行时常量池(Class 文件常量池的运行时表示)
- 静态变量(JDK 7 起从方法区移至堆,但仍在逻辑上属于方法区)
- JIT 编译后的代码缓存(CodeCache)
- 类加载器及其关联的元数据
升级陷阱: 数据库驱动升级后,若新驱动使用了大量匿名类、Lambda 表达式或动态代理,每个连接都会生成新的类加载器实例。这些类加载器无法被 GC 回收时,Metaspace 使用率只增不减,最终触发 Metaspace GC 甚至 OOM。
2.5 三区域关系与数据流
堆、栈、方法区并非孤立运行,它们通过引用和指针紧密协作:
- 栈帧中的局部变量引用堆中的对象实例。
- 堆中的对象实例通过
klass指针指向 Metaspace 中的类元数据。 - Metaspace 中的运行时常量池保存了字段和方法的符号引用,动态链接时解析为直接引用。
下面这张 SVG 图展示了三区域的核心交互关系:
2.6 升级场景下的内存模型变化
数据库迁移版本升级后,JVM 内存模型中的三个区域都会受到不同程度的影响:
| 区域 | 升级影响 | 典型表现 | 调优建议 |
|---|---|---|---|
| 堆 | 新驱动/ORM 可能产生更多临时对象,老年代晋升加速 | Young GC 频繁,老年代占用率快速上升 | 增大堆大小,调整新生代与老年代比例 |
| 栈 | 连接池线程数增加,栈深度可能变化 | 线程栈内存占用上升,偶发 StackOverflowError | 适当增大 -Xss,检查递归调用 |
| Metaspace | 类加载器数量激增,类元数据膨胀 | Metaspace GC 频繁,Full GC 停顿时间长 | 设置 -XX:MaxMetaspaceSize,监控类加载器泄漏 |