Java Runtime Data Area,即运行时数据区。一个 class 文件经过 ClassLoader 加载进入运行时数据区。
那么运行时数据区包含了什么信息呢?
Java Runtime Data Area 分布
整体分布
Program Counter Register
Program Counter Register 为每个线程独有。简称 PC 寄存器或者 PC 。用于记录当前线程执行到哪条指令。当线程被 CPU 调度执行的时候,会从当前线程的 PC 记录的指令编号继续执行。另外诸如分支、循环、跳转、异常等也都依赖于计数器。
JVM Stack
JVM Stack 为每个线程独有。JVM Stack 存放的是“栈帧”,每调用一次 Java 方法就会产生一个“栈帧”并推入 JVM Stack 中,方法结束后相应的“栈帧”弹栈。
由于“栈帧”对应具体的数据结构需要占用内存,所以当方法调用层级过高(例如:没有设置合理出口的递归调用),会发生“栈溢出”。
既然“栈帧”代表了一次方法调用,那么具体的数据结构装载些什么内容?一个完整的“栈帧”包含以下部分:
- 局部变量表:用于记录方法执行过程产生的局部变量。局部变量编号从 0 开始,对于一个实例方法而言,第 0 个局部变量为 this(注意不会有局部变量对应 super),再之后是方法的入参,最后才是方法开始执行才会有的局部变量。
- 操作栈:主要是存放执行指令所需要的参数和返回值。
- 动态链接:当该方法中需要引用一些局部变量表以外的数据时(例如 Method Area 中的类),需要有指向这些数据的链接。动态链接就是提供指向这些内容的链接。
- 返回值地址:当一个方法内部调用了别的方法时,需要暂停当前“栈帧”的执行,创建一个新的“栈帧”并执行,等待新“栈帧”执行完成,将结果放入当前“栈帧”的返回值地址处(通常为操作栈的栈顶),当前“栈帧”继续执行。
Native Method Stack
Native Method Stack 为每个线程独有。与 JVM Stack 类似,但 Native Method Stack 是服务与本地方法的(C/C++)栈结构,一般用于 JNI。
Heap
Heap 被所有线程共享。主要存放了通过 new 操作创建的对象。由于目前主流的垃圾回收器都是使用分代回收算法,所以堆内存通常也会被逻辑分区。
Heap 主要分为 Young 区(年轻代)和 Old 区(老年代),比例为 1 : 2。
其中 Young 区(年轻代)中分为一个 Eden 区 ,两个 Survivor 区(分别简称为 S0,S1),比例为 8 : 1 : 1。
关于 Heap 逻辑分区与垃圾回收的进一步介绍可以看:Java GC
Method Area
Method Area 是被所有线程共享的 Runtime Data Area。Method Area 是一个逻辑概念,是 JVM 所规范的。
Method Area 不像是栈或者堆有具体的数据结构对应。Method Area 在不同的 JDK 版本中实现有所不同:
- 在 JDK 1.8 之前: Method Area 的实现是永久代(PermGen)。主要存放类信息、常量池、静态变量和编译好的代码。 永久代不会被 GC 回收,由于类信息都放在永久代,所以容易导致永久代内存溢出(在运行期间不断动态创建新类,永久代内容逐渐被占满)。 永久代必须指定大小,大小受 -XX:PermSize 和 -XX:MaxPermSize 参数影响,而这两个参数又受 JVM 的整体堆内存大小限制(在 JDK 1.7,永久代在堆空间中)。因此永久代实现的 Method Area 有较大的可能性发生内存溢出。
- 在 JDK 1.8 之后:Method Area 的实现是元数据区(Metaspace)。 主要存放的内容和永久代一样,但是像类的元信息这些主要内容不再放在堆区,但是方法区中的静态变量和运行时常量池和 JDK 1.7 一样,仍在放在堆中。 元数据区可以不指定大小,也可以使用 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 参数指定,当不指定大小时,默认最大限制为物理内存大小(元数据区不在堆空间中,而是在本地内存中)。 由于元数据区的大小主要取决于物理内存大小,同时元数据区能够被 GC 回收,所以元数据区实现的 Method Area(相对于永久代实现的 Method Area )发生内存溢出的风险较低。
下图清楚展示了 JDK 1.7 & 1.8 堆空间划分的区别:
当同一个还没被加载的类被多个线程同时访问,这时只允许一个线程去加载它,另一个线程必须等待。
JDK 1.7 由于永久代在堆中,所以无论是 类的元信息、静态变量或者常量池都放在堆中,大小受限于堆内存,有较大的 OOM 风险。
JDK 1.8 取消了永久代,类的元信息等内容放在元空间中,元空间大小受限于物理内容。而静态变量和常量池则还是放在堆中。