浅谈JVM(四):运行时数据区
上一篇:
浅谈JVM(一):Class文件解析
浅谈JVM(二):类加载机制
浅谈JVM(三):类加载器和双亲委派
4.运行时数据区
Java虚拟机在执行程序的过程中会把它所管理的内存分成若干个不同的区域,称为运行时数据区(Run-Time Data Area)。运行时数据区主要包括:方法区(Method Area)、堆(Heap)、虚拟机栈(Java Vitrual Machine Stacks)、程序计数器(Program Counter Register)、本地方法栈(Native Method Stack)。
一些数据区域是线程共享的,在JVM启动时创建,只有在JVM退出时才会被销毁。还有一些区域是线程独享的,在创建线程时创建,在线程退出时销毁。
4.1.程序计数器
程序计数器寄存器(Program Counter Register),简称程序计数器或PC寄存器,是线程独享的一块内存空间,是当前线程所执行的字节码行号指示器。字节码解释器工作时通过改变程序计数器来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等都要依赖计数器完成。
一个线程在任意时刻只能执行一个方法,叫做当前方法(current method),如果当前方法不是本地方法(native method,非Java语言实现的方法),程序计数器记录当前虚拟机要执行的指令地址;如果当前方法的是本地方法,则程序计数器值为空(undefined)。程序计数器是虚拟机规范中唯一一个没有定义OutOfMemory的区域。
4.2.Java虚拟机栈
在第一版的Java虚拟机规范中,Java虚拟机栈(Java Virtual Machine Stack)被称作Java栈(Java Stack),用于保存局部变量和部分结果,并在方法调用和返回中发挥重要作用。每个线程都独享自己的虚拟机栈,栈中的存储单元是栈帧(Frames)。
方法是程序执行的最小单元,每个方法被执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态连接、方法出口(返回值、异常)等信息。每一个方法被调用直至退出的过程就对应着栈帧在虚拟机栈中从入栈到出栈的过程。
Java虚拟机规范中说,除了压入和弹出栈帧外,虚拟机栈并没有被直接操作过,所以栈帧的存储结构可以是堆结构,虚拟机栈的内存地址可以是不连续的。栈帧内部的详细结构将在下一篇文章中讲解。
4.3.堆
Java虚拟机中有一个线程共享的堆(Heap)空间,堆是一个运行时数据空间,其作用是用于存放对象实例,在虚拟机启动时创建。对象不会显式释放,对象的堆内存由一个自动的存储管理系统控制,也就是大家熟知的垃圾回收器(Garbage Collector,GC)。
堆的内存结构物理上不必连续。虚拟机规范没有对堆的内存管理方式提出明确的规定,垃圾回收器由虚拟机实现者自行设计。诸如"新生代"、"老年代"这样的空间划分,是HotSpot中曾经采用的设计方式,并不是虚拟机规范中定义的,也有不基于分代思想设计的垃圾回收器。
堆空间的大小可以是固定的,也可以是可扩展的,主流Java虚拟机都是按照可扩展的方式实现的(-Xmx和-Xms参数)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。垃圾回收和内存分配详细内容将在后续文章中讲解。
4.4.方法区
方法区(Method Area)是线程共享区域,在虚拟机启动时创建,用于存储运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。
虚拟机规范中说,方法区逻辑上是堆的一部分,但简单的实现可以选择不进行垃圾回收,虚拟机规范中没有强制要求方法区的位置。方法区的大小可以是固定的,也可以是扩展的。如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。
JDK 8以前,HotSpot将垃圾回收器的分代设计扩展到了方法区,这个实现叫做"永久代",JDK 8之后放弃了永久代,采用另一种实现策略,即"元空间"。
方法区中很重要的一部分是运行时常量池(Run-Time Constant Pool),类加载后,类文件中的常量池表(见浅谈JVM(一):Class文件解析)就会存在方法区的运行时常量池中。除了保存Class文件中描述的符号引用外,还会把由符号引用翻译出来的直接引用也存储在运行时常量池中。当常量池无法申请到内存时,会抛出OutOfMemoryError异常。
4.5.本地方法栈
本地方法栈(Native Method Stacks)的作用与虚拟机栈作用类似,区别是本地方法栈用于执行本地方法,Java中本地方法是由native修饰的方法,具体实现使用的是Java以外的语言。
本地方法栈的大小可以是固定的,也可以是可扩展的,本地方法栈也会在栈深度溢出时抛出StackOverflowError异常,栈扩展失败时抛出OutOfMemoryError异常。
虚拟机规范中没有要求本地方法栈如何实现,如HotSpot中就将本地方法栈和虚拟机栈合二为一。