Java 虚拟机的内存空间分为 5 个部分:
- 程序计数器
- Java 虚拟机栈
- 本地方法栈
- 堆
- 方法区
JDK 1.8 同 JDK 1.7 比,最大的差别就是:元数据区取代了永久代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元数据空间并不在虚拟机中,而是使用本地内存。
程序计数器(PC 寄存器)
程序计数器的定义
程序计数器是一块较小的内存空间,是当前线程正在执行的那条字节码指令的地址。若当前线程正在执行的是一个本地方法,那么此时程序计数器为Undefined
。
程序计数器的作用
- 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
- 在多线程情况下,程序计数器记录的是当前线程执行的位置,从而当线程切换回来时,就知道上次线程执行到哪了。
程序计数器的特点
- 是一块较小的内存空间。
- 线程私有,每条线程都有自己的程序计数器。
- 生命周期:随着线程的创建而创建,随着线程的结束而销毁。
- 是唯一一个不会出现
OutOfMemoryError
的内存区域。
Java 虚拟机栈(Java 栈)
Java 虚拟机栈的定义
Java 虚拟机栈是描述 Java 方法运行过程的内存模型。
Java 虚拟机栈会为每一个即将运行的 Java 方法创建一块叫做“栈帧”的区域,用于存放该方法运行过程中的一些信息,如:
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口信息
- ......
压栈出栈过程
当方法运行过程中需要创建局部变量时,就将局部变量的值存入栈帧中的局部变量表中。
Java 虚拟机栈的栈顶的栈帧是当前正在执行的活动栈,也就是当前正在执行的方法,PC 寄存器也会指向这个地址。只有这个活动的栈帧的本地变量可以被操作数栈使用,当在这个栈帧中调用另一个方法,与之对应的栈帧又会被创建,新创建的栈帧压入栈顶,变为当前的活动栈帧。
方法结束后,当前栈帧被移出,栈帧的返回值变成新的活动栈帧中操作数栈的一个操作数。如果没有返回值,那么新的活动栈帧中操作数栈的操作数没有变化。
由于 Java 虚拟机栈是与线程对应的,数据不是线程共享的(也就是线程私有的),因此不用关心数据一致性问题,也不会存在同步锁的问题。
局部变量表
定义为一个数字数组,主要用于存储方法参数、定义在方法体内部的局部变量,数据类型包括各类基本数据类型,对象引用,以及 return address 类型。
局部变量表容量大小是在编译期确定下来的。最基本的存储单元是 slot,32 位占用一个 slot,64 位类型(long 和 double)占用两个 slot。
对于 slot 的理解:
- JVM 虚拟机会为局部变量表中的每个 slot 都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值。
- 如果当前帧是由构造方法或者实例方法创建的,那么该对象引用 this,会存放在 index 为 0 的 slot 处,其余的参数表顺序继续排列。
- 栈帧中的局部变量表中的槽位是可以重复的,如果一个局部变量过了其作用域,那么其作用域之后申明的新的局部变量就有可能会复用过期局部变量的槽位,从而达到节省资源的目的。
在栈帧中,与性能调优关系最密切的部分,就是局部变量表,方法执行时,虚拟机使用局部变量表完成方法的传递局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。