JVM内存模型

§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,动态类加载失控会吃光物理内存。

堆内存分代结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
┌───────────────────────────────────────────┐
│                   堆 (Heap)                │
├─────────────────┬─────────────────────────┤
│   新生代(Young)  │      老年代(Old)         │
│  ┌────┬────┬───┐│                         │
│  │Eden│ S0 │ S1││                         │
│  │ 8  │ 1  │ 1 ││                         │
│  └────┴────┴───┘│                         │
│  默认比例 8:1:1  │                         │
├─────────────────┴─────────────────────────┤
│  新生代:老年代 ≈ 1:2 (默认 NewRatio=2)      │
└───────────────────────────────────────────┘
  • 新生代:新创建的对象。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变化。

本作品采用 CC BY-NC-SA 4.0 协议进行许可
使用 Hugo 构建
主题 StackJimmy 设计