§2.1.1 JVM内存模型
考察意图:是否理解各内存区域的职责、存储内容、以及OOM的发生场景。
回答样板:
JVM运行时数据区分为线程私有和线程共享两大类,共5个核心区域:
| 区域 | 共享性 | 存储内容 | OOM场景 |
|---|---|---|---|
| 程序计数器 | 线程私有 | 当前线程执行的字节码行号指示器 | 唯一不会OOM的区域 |
| 虚拟机栈 | 线程私有 | 栈帧(局部变量表+操作数栈+动态链接+返回地址) | 线程过多或递归过深:StackOverflowError |
| 本地方法栈 | 线程私有 | Native方法的栈帧 | 与虚拟机栈类似 |
| 堆(Heap) | 线程共享 | 对象实例、数组 | 对象过多且GC无法回收:java.lang.OutOfMemoryError: Java heap space |
| 方法区(元空间) | 线程共享 | 类元数据、静态变量、常量池、JIT编译缓存 | 动态生成类过多或常量池溢出:OOM: Metaspace |
JDK 8关键变化——永久代→元空间:
JDK 7及之前,方法区用永久代(PermGen)实现,属于JVM堆内存的一部分,受-XX:MaxPermSize限制。JDK 8移除了永久代,改用本地内存中的元空间(Metaspace),默认只受物理内存限制。好处:之前永久代大小难以预估(加载的类数量不确定),经常导致OOM: PermGen space。改用元空间后,类元数据放在堆外的本地内存,容量弹性更大。但风险是——如果不设-XX:MaxMetaspaceSize,动态类加载失控会吃光物理内存。
堆内存分代结构:
| |
- 新生代:新创建的对象。Eden区(80%)+ Survivor 0(10%)+ Survivor 1(10%)
- 老年代:长期存活对象。经历多次Minor GC仍存活的对象晋升至此
- 对象晋升流程:Eden分配 → 首次Minor GC存活进S区 → 每次Minor GC存活Age+1 → Age达到
MaxTenuringThreshold(默认15)→ 晋升老年代。动态年龄判定:S区中同龄对象总大小超过S区50%时,该年龄及以上对象直接晋升
为什么需要两个Survivor区? 解决内存碎片化——每次Minor GC,将Eden+From Survivor存活对象copy到To Survivor,然后清空Eden和From,From和To角色互换。始终保持一块Survivor为空,避免碎片化。这就是复制算法在新生代的落地。
陷阱提示:说不清各个区域存什么;把方法区和元空间等同(元空间是实现,方法区是规范);不知道永久代→元空间的JDK 8变化。