-
线程私有
-
即通常所说的“栈空间”
-
线程中的每个方法被执行时,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口(方法返回地址)等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
● 一个栈帧需要分配多少内存,并不会受到程序运行期变量数据的影响,而仅仅取决于程序源码和具体的虚拟机实现的栈内存布局形式
● 即,需要分配多少内存,编译期间就已经知道了,不能动态更改
-
局部变量表
● 局部变量表是GC Root 的一部分
● 用于存放方法参数和方法内部定义的局部变量
● 在Java程序被编译为Class文件时,就已经在方法的Code属性的max_locals(locals)数据项中确定了该方法所需分配的局部变量表的最大容量
● 局部变量表的基本存储单位为变量槽(slot)
● 局部变量表的“大小”,是以变量槽的数量决定的,而变量槽的大小,各个虚拟机的实现可能不同。比如一个槽可能占32bit或64bit
● 其中,大小为64bit的long和double,在局部变量表中会占用2个槽, 其余的,byte、short、int、float、char、boolean、reference、referenceAddress 这8种类型每个只占用一个变量槽
※ 注意⚠️,reference 类型,是指对一个对象实例的引用,作用:一是根据引用,直接或间接地查找到对象在Java堆中的数据存放的起始地址或索引,二是根据引用,直接或间接地查找到对象所属数据类型在方法区中存储的类型信息
※ reference 类型,在32位虚拟机上是32位,在64位虚拟机上是64位,但并不总是这样,如果开启了虚拟机的指针压缩,则实际上的大小会小一些(一般为一半)
● 局部变量表的变量槽是可以复用的
● 如上所示,执行gc后,placeholder对象占用的64M内存并没有被回收,因为在执行gc()时,placeholder依然处于作用域内(main方法退出前),因此局部变量表依然保留着对堆中placeholder对象的引用,即上面的reference依然在局部变量表的变量槽中
●如上所示,placeholder的作用域被限制在大括号内,在执行gc时,placeholder已经无用,但是在整个方法退出前,由于没有其他操作,局部变量表的变量槽中指向placeholder的reference不会变动,因此相当于还保留着对64M内存的引用,因此无法垃圾回收● 如上所示,虽然placeholder看起来像是被限制了作用域(在大括号内),但执行结果其实和①的代码一样
● 但两种方式下,生成的字节码是不同的
● 第一种:
● 第二种
● 第一种placeholder占用了一个变量槽,第二种没有占用;gc的结果是相同的:placeholder对象占用的64M内存都没有在gc时回收掉● 对比两种代码的常量池
● 第一种
● 第二种
●第一种比第二种多了17、18号常量,表示byte数组,即:被限制在{}内的数据,不会进入常量池,也不会占用局部变量表● 但实际上,{}包起来的placeholder在运行时是占用一个变量槽的,只是其作用域只在{}中,但参数args的作用域却直到方法结束,因此,字节码层面上,只显示占用了一个变量槽,即参数args占用的(※这里有问题※)
● 若想在方法退出前,成功执行对placeholder的垃圾回收,则需要将placeholder隐式❓占用的变量槽让其他变量占用,这样,局部变量表就会在执行gc前失去对placeholder的引用,因此会被垃圾回收,如下,即可被垃圾回收
● 但,如果placeholder不在{}内,则不会被垃圾回收,因为在执行gc时,局部变量表依然保持着对placeholder的引用,无法回收
-
操作数栈
● 每一个栈帧中都有一个操作数栈
● 同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks(stacks)数据项之中
● 32位数据类型所占的操作数栈容量为1,64位数据类型所占的操作数栈容量为2
● 在调用其他方法的时候是通过操作数栈来进行方法参数的传递
● 虚拟机栈中的栈帧和栈帧之间是可以重叠的,而并非完全的相互独立
● 让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样做不仅节约了一些空间,更重要的是在进行方法调用时就可以直接共用一部分数据,无须进行额外的参数复制传递了
-
动态连接
● 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,这个引用是为了支持方法调用过程中的动态连接(DynamicLinking)
● 这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析
● 另外一部分符号引用将在每一次运行期间都转化为直接引用,这部分就称为动态连接
-
方法返回地址
● 一个方法执行后,有两种方式退出方法:
● 正常调用完成
● 异常调用完成(一个方法使用异常完成出口的方式退出,是不会给它的上层调用者提供任何返回值的)
● 无论方法如何退出,都必须返回到最初方法被调用时的位置,程序才能继续执行
● 一般来说,方法正常退出时,主调方法的PC计数器的值就可以作为返回地址
● 方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息
● 即,只有在方法的退出方式为 “正常调用完成” 时,栈帧中才会保留方法返回地址
● 方法退出的过程实际上等同于把当前栈帧出栈,因此退出时“可能”执行的操作有:(具体的虚拟机实现可能有所不同)
● 恢复上层方法的局部变量表和操作数栈
● 把返回值(如果有的话)压入方法调用者栈帧的操作数栈中
● 调整PC计数器的值,以指向方法调用指令后面的一条指令等
JVM(笔记)——【运行时数据区】Java虚拟机栈
最新推荐文章于 2024-07-09 21:50:43 发布