本篇我们主要总结一下运行时栈帧结构以及编码器优化技术。
一、运行时栈帧结构
Java虚拟机以方法作为最基本的执行单元,“栈帧”(Stack Frame)则是用于支持虚拟机进行方法调用和方法执行背后的数据结构,它也是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。
每 一个方法从调用开始至执行结束的过程,都对应着一个栈帧在虚拟机栈里面从入栈到出栈的过程。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址和一些额外的附加信息。
在这里我们主要讲解一下局部变量表。
-
局部变量表
局部变量表(Local Variables Table)是一组变量值的存储空间,用于存放方法参数 和 方法内部定义的局部变量。
局部变量表的容量以变量槽(Variable Slot)为最小单位。一个变量槽可以存放一个 32位以内的数据类型,Java中占用不超过32位存储空间的数据类型有boolean、byte、char、short、int、 float、reference和returnAddress(目前很少见)这8种类型。
- reference:reference类型表示对一个对象实例的引用,根据引用直接或间接地查找到对象在Java堆中的数据存放的起始地址或索引。
- ⭐Java虚拟机通过索引定位的方式使用局部变量表。
- 当一个方法被调用时,Java虚拟机会使用局部变量表来完成参数值到参数变量列表的传递过程, 即实参到形参的传递。
- 对于方法内来讲,变量名字并不重要
-
操作数栈
- 操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出栈。
- 当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。
- 操作数栈中元素的数据类型必须与字节码指令的序列严格匹配
-
动态链接
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。
- 这些符号 引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。 另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。
-
方法返回地址
-
附加信息(具体请详细参考深入理解Java虚拟机第三版)
二、编译器优化技术
几种优化技术包括有:方法内联、逃逸分析、公共子表达式消除以及数组边界检查消除。
- 方法内联(优化之母)
- 去除方法调用的成本
- 为其他优化建立良好的基础
- 逃逸分析
- 基本原理:
- 分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部 方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;
- 如果能证明一个对象不会逃逸到方法或线程之外(换句话说是别的方法或线程无法通过任何途径 访问到这个对象),或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可能为这个对象实例采取不同程度的优化 :
- 栈上分配
- 在Java虚拟机中,对象的内存空间基本上都是在堆上分配的。
- Java堆中的对象对于各个线程都是共享和可见的,只要持有这个对象的引用,就可以访问到堆中存储的对象数据。虚拟机的垃圾收集子系统会回收堆中不再使用的对象,但回收动作无论是标记筛选出可回收对象,还是回收和整理内存,都需要耗费大量资源。如果确定一个对象不会逃逸出线程之外,那让这个对象在栈上分配内存将会是一个很不错的主意,对象所占用的内存空间就可以随栈帧出栈而销毁。
- 栈上分配可以支持方法逃逸,但不能支持线程逃逸。
- 标量替换
- 若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据 就可以被称为标量。相对的,如果一个数据可以继续分解,那它就被称为聚合量(Aggregate),Java中的对象就是典型的聚合量。如果把一个Java对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,这个过程就称为标量替换。
- 对逃逸程度的要求更高,它不允许对象逃逸出方法范围内
- 同步消除
- 线程同步本身是一个相对耗时的过程,如果逃逸分析 能够确定一个变量不会逃逸出线程,无法被其他线程访问,那么这个变量的读写肯定就不会有竞争, 对这个变量实施的同步措施也就可以安全地消除掉。
- 栈上分配
- 基本原理: