JVM 内存结构
首先给出 JVM 的一个整体架构1:
本文所研究的JVM的内存结构就是中间的运行时数据区。
JVM 内存结构是指:Java 虚拟机定义了若干种程序运行期间会使用的运行时数据区,其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁,另一些则与线程一一对应,随着线程的开始而创建,随着线程的结束而销毁2。
在 Java 虚拟机规范中,定义了若干种运行时数据区。
- 线程私有:程序计数器、虚拟机栈、本地方法区
- 线程共享:堆、方法区、堆外内存(Java7的永久代或JDK8的元空间、代码缓存)
程序计数器 Program Counter Register
JVM 中的 PC 寄存器是对物理 PC 寄存器的一种抽象模拟。
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。
它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域
在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期一致
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。如果当前线程正在执行的是 Java 方法,程序计数器记录的是 JVM 字节码指令地址,如果是执行 native 方法,则是未指定值(undefined)
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
程序计数器是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError
情况的区域。
虚拟机栈 JVM Stacks
Java 虚拟机栈,早期也叫 Java 栈。
每个线程在创建的时候都会创建一个虚拟机栈,对应着一次次 Java 方法调用,是线程私有的,生命周期和线程一致。
每一个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中的入栈到出栈的过程。
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
栈不存在垃圾回收问题。
Java 虚拟机规范允许 Java虚拟机栈的大小是动态的或者是固定不变的。
- 如果采用固定大小的 Java 虚拟机栈,那每个线程的 Java 虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量,Java 虚拟机将会抛出一个 StackOverflowError 异常
- 如果 Java 虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个OutOfMemoryError异常
可以通过参数-Xss
来设置线程的最大栈空间,栈的大小直接决定了函数调用的最大可达深度。
JVM 直接对 Java 栈的操作只有两个,对栈帧的压栈和出栈。
在一条活动线程中,一个时间点上,只会有一个活动的栈帧。即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。
不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧中引用另外一个线程的栈帧。
Java 方法有两种方法返回的方式,一种是正常的方法返回,使用 return 指令,另一种是抛出异常,不管用哪种方式,都会导致栈帧被弹出。
栈帧的内部结构如下图所示,主要有局部变量表、操作数栈、方法返回地址、动态链接等: