今天我们一起来学习一下JVM内存模型中非常重要的一部分:运行时数据区
接下来我们将带着一下几个问题来共同学习这一部分。
一、什么是运行时数据区?
二、运行时数据区都包括哪些区域?
三、各个区域的作用是什么?
好了,接下来我们先来解答一下第一个问题:什么是运行数据区?
Java虚拟机在执行Java程序的过程中,会将涉及的数据划分到不同的内存区域去管理,而这部分区域就是我们接下来要主要讲解的运行时数据区,也就是Java虚拟机的运行时数据区。
第二个问题:运行时数据区都包括那几个区域?
1、线程私有区域:1) 程序计数器 2) 虚拟机栈 3) 本地方方法栈
2、线程共享区域:4) 方法区 5) 堆
接下来我们来看第三个问题:各个区域的作用是什么?
1、程序计数器
程序计数器是线程私有内存,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。它可以看作是当前线程所执行的字节码的行号指示器。程序执行过程中,通过改变这个计数器的值来选取下一条需要执行的字节码指令来完成下一步的操作。
每条线程都有一个独立的程序计数器,各线程之间互不影响,独立存储,故称之为线程私有内存。线程私有内存的生命周期与线程相同,线程的结束也就意味着内存生命周期的完结。
2、虚拟机栈
虚拟机栈也是线程私有内存,虚拟机栈描述的是java方法执行的内存模型。整个线程执行过程中,每个方法对应着虚拟机栈中的一个栈帧,方法调用对应着一个栈帧的入栈,方法执行结束对应着一个栈帧的出栈。
栈帧:用于支持虚拟机进行方法调用和方法执行的数据结构。
每个方法在执行的同时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法返回地址等信息。
局部变量表:栈帧中一块用来存储方法中的方法参数和局部变量的区域。
局部变量表的大小在编译期间(由java文件变成class文件的过程)完成计算的,并写入到方法表的Code属性的max_locals中,表示需要分配给局部变量表的最大容量。局部变量表的大小不会受到运行期的影响。
局部变量表的容量以变量槽(slot)为最小单位,虚拟机规范中没有明确指明一个Slot应占用的内存空间大小,只是说到每个slot应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,这8种数据类型都可以使用32位或者更小的物理内存来存放。
其中,reference对应着一个对象实例的引用,虚拟机实现至少应当能够通过这个引用做到两点,一是虚拟机可以根据该引用直接或间接的找到对象实例在java堆中数据存放起始地址的索引。二是根据该引用可以直接或间接的找到对象所属类型在方法区中存储的类型信息。returnAddress类型目前已经少见,已经由异常表代替。
另外值得注意的是,long和double占用2个局部变量空间(slot),long和doble数据类型被分割存储(非原子性协定),也就是说把一次long和double数据类型读写分割为两次32位读写。
在局部变量表的第0位索引的Slot默认存放当前方法所属对象实例的引用。也就是我们常用的this。
为了节省栈帧空间,局部变量表中的Slot是可以重用的。那么我们该如何重用这个部分区域呢?在一个方法中,我们定义的变量的作用域不一定会涵盖整个方法体,所以,如果当前字节码PC计数器的值已经超出了某个变量的作用域,那个这个变量对应的Slot就可以被其他变量来复用了。(另外,Slot的复用也会影响到GC行为,该部分以后补充)
操作数栈:也被称为操作栈,是栈帧中用来执行计算的一块区域。
操作数栈的容量大小同局部变量表一样,也是在编译期间完成计算的。操作数栈的最大深度在编译的时候被写入到Code属性的max_locals数据项中。操作数栈中的类型可以是任意的java数据类型,32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2.
当一个方法刚刚开始执行的时候,这个方法的操作栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈的操作。
动态链接:每个栈帧中都包含一个指向运行时常量池中该栈帧所属方法的引用,这个引用在运行期间转化为直接引用,来获取方法的元信息。
符号引号,一部分会在类加载阶段或者第一次使用的时候(new一个实例对象时)就转化为直接引用,这种转化成为静态解析。另外一部分将在每一次运行期间转化为直接引用,这部分称为动态链接。
方法返回地址:一个方法执行结束后应该回到的位置地址。
方法执行开始后,只有两种方式可以退出。一种是正常退出,返回到上一层方法调用的位置,此时调用者的PC计数器的值可以作为返回地址。一种是异常退出,返回地址是要通过异常处理器表来确定的,栈帧中不会一般不会保存这部分信息。
3、本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行java方法,而本地方法栈则为虚拟机执行Native方法。
在Sun HotSpot虚拟机中,直接把本地方法栈和虚拟机栈合二为一。
4、Java堆
Java堆是被所有线程共享的一块内存区域,在虚拟机启动式创建。此内存区域的唯一目的就是存放对象实例(以及数组)。
Java堆是垃圾收集器管理的主要区域,也被成为GC堆。
5、方法区
方法区与Java 堆一样,也是被所有线程共享的一块内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译器编译后的代码等数据。
运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。