在Java虚拟机的概念中,运行时数据区又被称为Java内存区域。主要由线程私有的程序计数器、虚拟机栈和本地方法栈以及线程共享的堆和方法区组成。
程序计数器
程序计数器在Java内存中占据的空间比较小,是当前线程正在执行的字节码的行号指示器。字节码指令执行的过程中,分支、循环、跳转、异常处理、线程恢复等都需要通过程序计数器来完成。
在某一个时刻,一个内核只会执行一个线程中的指令,因此需要程序计数器来确保多线程切换后回复到某一线程能够继续之前的指令向后执行。因此,这也就决定了程序计数器必须是线程私有的。
当线程执行的是Java方法时,程序计数器保存当前正在执行的指令的地址,而当线程执行的是native方法时,程序计数器的值为undefined
。
另外值得一提的是,程序计数器是运行时数据区中唯一不会出现OutOfMemoryError
的。
Java虚拟机栈
当一个线程执行到某一个方法时,对应的是Java虚拟机创建一个栈帧,并将该栈帧压入虚拟机栈的过程。相对应的,方法结束则意味着虚拟机栈弹出该栈帧。
显而易见的是,局部变量表,存储的是方法中的局部变量。当局部变量是基本数据类型时,直接存储该变量的值,如果是引用数据类型,则存储对象引用。局部变量表在栈帧中分配的空间在编译阶段就已经确认,因此不会在方法运行期间改变大小。
本地方法栈
虚拟机栈和本地方法栈的原理十分相似,区别在于虚拟机栈执行的是Java方法,而本地方法栈执行的是Native方法。
由于JVM规范没有强制规定本地方法栈的实现,HotSpot虚拟机直接将本地方法栈和虚拟机栈合二为一。
堆
对于C语言而言,堆是程序员可以直接管理的区域。程序员通过malloc
和free
方法申请和释放内存。
对于Java而言,堆存储着对象及对象数组。因此,也是垃圾回收器主要管理的区域。
根据JVM的规范,Java堆可以存储在物理不连续的区域,但逻辑上是连续的。对于大对象(如数组对象),很多虚拟机直接将它们存储在物理上连续的内存区域。另外,堆是所有线程共享的区域,因此JVM中只有一个堆。
方法区
方法区和堆一样,都是线程共享的区域。方法区存放了类的信息(类名、字段信息、方法信息)、静态变量、常量以及编译后的代码缓存等数据。
很多人将方法区和“永久代”混为一谈,原因是HotSpot把垃圾收集器的设计扩展到了方法区,从而省去为方法区专门编写内存管理代码的工作。
运行时常量池是方法区的一部分,用于存放常量池表,常量池表中存放了编译期生成的各种符号字面量和符号引用。