媒介
Java 虚拟机的内存模子分为两部门:一部门是线程共享的,包罗 Java 堆和要领区;另一部门是线程私有的,包罗虚拟机栈和当处所法栈,以及措施计数器这一小部门内存。本日我就 Java 虚拟机栈做一些较量浅的探究。
熟悉 Java 的同学应该都知道了,JVM 是基于栈的。可是这个“栈” 详细指的是什么?莫非就是虚拟机栈?想要答复这个问题我们先要从虚拟机栈的布局谈起。
虚拟机栈
作甚虚拟机栈
虚拟机栈的栈元素是栈帧,当有一个要领被挪用时,代表这个要领的栈帧入栈;当这个要领返回时,其栈帧出栈。因此,虚拟机栈中栈帧的入栈顺序就是要领挪用顺序。什么是栈帧呢?栈帧可以领略为一个要领的运行空间。它主要由两部门组成,一部门是局部变量表,要领中界说的局部变量以及要领的参数就存放在这张表中;另一部门是操纵数栈,用来存放操纵数。我们知道,Java 措施编译之后就酿成了一条条字节码指令,其形式雷同汇编,但和汇编有差异之处:汇编指令的操纵数存放在数据段和寄存器中,图纸加密,可通过存储器或寄存器寻址找到需要的操纵数;而 Java 字节码指令的操纵数存放在操纵数栈中,当执行某条带 n 个操纵数的指令时,就从栈顶取 n 个操纵数,然后把指令的计较功效(假如有的话)入栈。因此,当我们说 JVM 执行引擎是基于栈的时候,个中的“栈”指的就是操纵数栈。举个简朴的例子比拟下汇编指令和 Java 字节码指令的执行进程,好比计较 1 + 2,在汇编指令是这样的:
mov ax, 1 ;把 1 放入寄存器 ax add ax, 2 ;用 ax 的内容和 2 相加后存入 ax
而 JVM 的字节码指令是这样的:
iconst_1 //把整数 1 压入操纵数栈 iconst_2 //把整数 2 压入操纵数栈 iadd //栈顶的两个数相加后出栈,功效入栈
由于操纵数栈是内存空间,所以字节码指令不必担忧差异呆板上寄存器以及呆板指令的不同,从而做到了平台无关。
留意,局部变量表中的变量不行直接利用,如需利用必需通过相关指令将其加载至操纵数栈中作为操纵数利用。好比有一个要领 void foo(),个中的代码为:int a = 1 + 2; int b = a + 3;,编译为字节码指令就是这样的:
iconst_1 //把整数 1 压入操纵数栈 iconst_2 //把整数 2 压入操纵数栈 iadd //栈顶的两个数出栈后相加,功效入栈;实际上前三步会被编译器优化为:iconst_3 istore_1 //把栈顶的内容放入局部变量表中索引为 1 的 slot 中,也就是 a 对应的空间中 iload_1 // 把局部变量表索引为 1 的 slot 中存放的变量值(3)加载至操纵数栈 iconst_3 iadd //栈顶的两个数出栈后相加,功效入栈 istore_2 // 把栈顶的内容放入局部变量表中索引为 2 的 slot 中,也就是 b 对应的空间中 return // 要领返回指令,回到挪用点
需要说明的是,局部变量表以及操纵数栈的容量的最大值在编译时就已经确定了,运行时不会改变。而且局部变量表的空间是可以复用的,譬喻,当指令的位置超出结局部变量表中某个变量 a 的浸染域时,假如有新的局部变量 b 要被界说,b 就会包围 a 在局部变量表的空间。
盗用别人的图以让各人对虚拟机栈有个直观的认识(个中小字体 Stack 指的的是虚拟机栈,Frame 是栈帧,Local variables 是局部变量表,Operand Stack 是操纵数栈):
虚拟机栈
由虚拟机栈引出的问题
看完上面的代码各人大概会有几点迷惑:什么是 slot?那些指令是什么意思?为什么 a 对应的 slot 的索引值不是从零开始的,它显着是第一个界说的变量啊?
对付这些问题我们一个个来办理。
什么是 slot
首先什么是 slot?slot 是局部变量表中的空间单元,虚拟机类型中有划定,对付 32 位之内的数据,用一个 slot 来存放,如 int,short,float 等;对付 64 位的数据用持续的两个 slot 来存放,如 long,double 等。引用范例的变量 JVM 并没有划定其长度,它大概是 32 位,也有大概是 64 位的,所以既有大概占一个 slot,也有大概占两个 slot。
JVM 字节码指令
第二个问题,那些指令是什么意思?
指令名目
首先我们要领略 Java 指令的名目,Java 的指令以字节为单元,也就是一个字节代表一条指令。好比 iconst_1 就是一条指令,它占一个字节,那么自然 Java 指令不会高出 256 条。实际上 Java 指令今朝界说了 200 多条。指令固然是一个字节,可是它也可以带本身的操纵数。JVM 中有这样一条指令 putstatic,其浸染是给特定的的静态字段赋值。可是给哪个字段赋值呢?仅仅通过这条指令并不能说明,那么只有通过操纵数来指定了。紧跟在 putstatic 后头的两个字节就是它的操纵数,这个操纵数是一个索引值,指向运行时常量池中该静态字段对应的标记引用。由于标记引用包括了该字段的根基信息,如所属类、简朴名称以及描写符,因此 putstatic 指令就知道是给哪个类的哪个字段赋值了。