运行时数据区总结与大厂面试题
JVM 运行时数据区全图
在学完运行时数据区的所有章节后,让我们用一张全景图做最后的总结。
┌─────────────────────────────────────────────────────────────────┐
│ JVM 运行时数据区 │
│ │
│ ┌──────────────────┐ ┌────────────────────────────────┐ │
│ │ 线程私有区域 │ │ 线程共享区域 │ │
│ │ │ │ │ │
│ │ ┌────────────┐ │ │ ┌──────────────────────┐ │ │
│ │ │ PC 寄存器 │ │ │ │ 堆(Heap) │ │ │
│ │ └────────────┘ │ │ │ Eden │ S0 │ S1 │ Old │ │ │
│ │ │ │ └──────────────────────┘ │ │
│ │ ┌────────────┐ │ │ ┌──────────────────────┐ │ │
│ │ │ 虚拟机栈 │ │ │ │ 方法区 / 元空间 │ │ │
│ │ │ 栈帧/局部变量│ │ │ │ 类信息/常量池/代码缓存│ │ │
│ │ └────────────┘ │ │ └──────────────────────┘ │ │
│ │ │ │ ┌──────────────────────┐ │ │
│ │ ┌────────────┐ │ │ │ 直接内存 │ │ │
│ │ │ 本地方法栈 │ │ │ │ NIO / Netty / Kafka│ │ │
│ │ └────────────┘ │ │ └──────────────────────┘ │ │
│ └──────────────────┘ └────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘核心对比总结
线程私有 vs 线程共享
| 维度 | 线程私有 | 线程共享 |
|---|---|---|
| 区域 | PC 寄存器、虚拟机栈、本地方法栈 | 堆、方法区、直接内存 |
| 生命周期 | 与线程共存亡 | 与 JVM 进程共存亡 |
| GC | 无需 GC(线程结束即释放) | GC 管理 |
| 线程安全 | 天然线程安全 | 需要考虑并发 |
| 异常 | StackOverflowError | OutOfMemoryError |
堆 vs 方法区
| 维度 | 堆(Heap) | 方法区(Metaspace) |
|---|---|---|
| 存储内容 | 对象实例、数组 | 类信息、字节码、常量、代码缓存 |
| JDK 8+ 位置 | JVM 堆 | 本地内存 |
| GC 频率 | 高(MinorGC / FullGC) | 低(主要 FullGC 时) |
| OOM 原因 | Java heap space | Metaspace |
大厂面试题精选
Q1:运行时数据区有哪些?哪些是线程私有的?
答案:
- 线程私有:PC 寄存器、虚拟机栈、本地方法栈
- 线程共享:堆、方法区/元空间、直接内存(严格说不是 JVM 规范的一部分)
Q2:对象的创建过程是怎样的?
答案:
- 检查类是否已加载
- 在堆中分配内存(TLAB / 指针碰撞)
- 设置对象头(Mark Word + 类型指针)
- 执行构造器
<init> - 返回引用
Q3:什么情况下会 OOM?OOM 的类型有哪些?
答案:
| 类型 | 原因 |
|---|---|
Java heap space | 堆内存耗尽(分配对象过多、内存泄漏) |
Metaspace | 元空间耗尽(加载类过多) |
Direct buffer memory | 直接内存耗尽(NIO 使用过多) |
Unable to create new native thread | 线程数过多,栈空间不足 |
Requested array size exceeds VM limit | 数组过大,超过了 JVM 允许的最大数组大小 |
Q4:StringTable 在哪个区域?
答案:
- JDK 7+:堆(从 PermGen 移入)
- JDK 6:PermGen(堆内)
注意:字符串常量池(StringTable)是运行时常量池的一部分,但它在 JDK 7 时被移入了堆。
Q5:GC 在什么时候会影响用户线程?
答案:所有 GC 都会产生 Stop-The-World(STW)。
- MinorGC:短暂的 STW(几十毫秒)
- FullGC:较长的 STW(数百毫秒到秒级)
- CMS/G1:部分阶段并发执行,STW 时间缩短
- ZGC/Shenandoah:STW 时间极短(亚毫秒级)
Q6:虚拟机栈和本地方法栈有什么区别?
答案:
- 虚拟机栈:为 Java 方法服务,存储栈帧(局部变量表、操作数栈、动态链接、返回地址)
- 本地方法栈:为 native 方法服务,技术上可以分离,但 HotSpot 将两者合二为一
Q7:对象的内存布局是什么?
答案:对象在堆中的内存布局分为三部分:
- 对象头(Header):Mark Word(哈希、分代年龄、锁信息)+ 类型指针(指向类元数据)
- 实例数据(Instance Data):对象字段的内容
- 对齐填充(Padding):保证对象大小是 8 字节的倍数
Q8:直接内存是什么?有什么特点?
答案:
- 直接内存是操作系统分配的本地内存,不属于 JVM 堆
- 通过
ByteBuffer.allocateDirect()分配 - 用途:高性能 IO(Netty、Kafka)
- 不受
-Xmx控制,受-XX:MaxDirectMemorySize控制 - 超出限制抛出
OutOfMemoryError: Direct buffer memory
场景化面试题
场景题一:服务器 CPU 100%,如何定位?
思路:
top/jps找到进程jstack <pid>查看线程堆栈jstat -gc <pid>查看 GC 情况jmap -heap <pid>查看堆使用jmap -dump:file=/tmp/heap.hprof <pid>导出堆文件分析
场景题二:如何排查 OOM?
思路:
- 开启
-XX:+HeapDumpOnOutOfMemoryError - 分析堆转储(MAT / JProfiler)
- 找到占用最大的对象
- 定位引用链路(Who holds the reference?)
- 修复代码
场景题三:如何减少 FullGC 频率?
思路:
- 减少对象分配频率(对象池、缓存)
- 增大堆或年轻代(减少 GC 频率)
- 减少大对象直接进入老年代(
-XX:PretenureSizeThreshold) - 使用 G1 或 ZGC(减少 FullGC 停顿)
本节小结
运行时数据区的核心知识点:
| 区域 | 存储内容 | 线程关系 | GC |
|---|---|---|---|
| PC 寄存器 | 当前指令地址 | 私有 | 无 |
| 虚拟机栈 | 栈帧/局部变量/操作数栈 | 私有 | 无 |
| 本地方法栈 | native 方法栈帧 | 私有 | 无 |
| 堆 | 对象实例/数组 | 共享 | 是 |
| 方法区/元空间 | 类信息/常量/代码 | 共享 | 是 |
| 直接内存 | NIO 缓冲区 | 共享 | Cleaner |
通过这张全景图和面试题,你可以系统性地回顾运行时数据区的所有知识点。
到这里,「JVM 运行时数据区」部分全部完成。接下来进入 对象实例化专题。
