1.栈帧是用于支持虚拟机进行方法调用和方法只想执行的数据结构,它是虚拟机是运行时数据区中的虚拟机栈的栈元素.
每一个栈帧都包含了局部变量表,操作数表,动态连接,方法返回地址和一些额外的附加信息.在编译代码的时候,栈帧需
要多大的局部变量表,多深的操作数栈已经完全确定,并且写入到方法表的Code属性中.
2.对于执行引擎来说,在活动线程中,只有位于栈顶的栈帧才是有效的,执行引擎的所有字节码指令只针对当前栈帧进行操作
3.局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量.局部变量表的容量以Slot为最小单位
在方法执行时,虚拟机是使用局部变量表完成参数值到参数变量列表的传递过程,如果执行的是实例方法,那么局部变量表
中第0位索引的Slot默认是用于传递方法所属对象实例的引用,在方法中可以通过this来访问到这个隐含的参数.
4.为了尽可能节省帧空间,局部变量表中的Slot是可以重用的,这可能导致垃圾回收的过程中,如果没有任何对局部变量表的
读写操作,被占用的Slot空间没有被其他变量所复用,所以GC Roots一部分的局部变量表仍保持着该变量的关联,其所占的
空间没有被回收,这时可以手动将其设为null.
5.操作数栈的最大深度在编译的时候写入到Code属性中,当一个方法刚刚开始的时候,这个方法的操作数栈是空的.
6.在概念模型中,两个栈帧作为虚拟机栈的元素,是完全互相独立的,但在大多数虚拟机的实现里,会令两个栈帧出现一部分重叠
让下面的栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠,这样进行方法调用时就可以共用一部分数据,无需进行额外
的参数复制
7.方法的返回有两种方式
1. 当遇到任意一个方法返回字节码指令,这时候可能会有返回值传递给上层的方法调用者,是否有返回值和返回值的类型由遇到
何种方法返回指令决定,称为正常完成出口
2.在方法执行过程中遇到异常,并且这个异常在方法体内没有处理,就会导致方法退出,称为异常完成返回,这时是不会给他的上层
调用者产生任何的返回值
8.方法调用不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用那一个方法),暂时还不涉及方法内部的
具体运行过程.一切方法调用在Class文件里面存储的都只是符号引用,而不是方法实际运行是内存布局中的入口地址(直接引用)
需要在类加载期间,甚至到运行时才能确定目标方法的直接引用
9.调用目标在程序代码写好,.编译器进行编译时就能确定下来,这类方法的调用称为解析 .主要包含静态方法和私有方法,这两种方法
各自的特点决定了他们都不可能通过继承或者别的方式重写其他版本.因此他们都适合在类加载阶段进行解析
与之相对应的是,在java虚拟机中提供了5条方法调用字节码指令:
(1)invokestaic : 调用静态方法
(2)invokespecial : 调用实例构造器<init>(),私有方法和父类方法
(3)invokeinterface : 调用接口方法.会在运行时在确定一个此接口的对象
(4)invokevirtual : 调用所有的虚函数
(5)invokedynamic : 先在运行是动态解析出调用点限定符 所引用的方法,再执行
10.只要被invokestatic 和 invokespecial指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合条件的有静态方法,私有方法
实例构造器,父类构造器,它们会在类加载阶段的时候就会把符号引用解析为该方法的直接引..注意到final方法是使用invokevirtual
指令调用,属于非虚方法
11.静态分派(重载):
如Human man = new Man();
Human为静态类型,或者叫外观类型,Man为实际类型.区别在于静态类型的变化仅仅在使用的时候变化,变量本身的静态类型不会改变,
并且最终的静态类型是在编译期可知, 而实际类型变化的结果在运行期才确定,
虚拟机(编译器)在重载时是通过参数的静态类型而不是实际类型作为判断 ,而且静态类型在编译期可知
所有依赖静态类型来定位方法执行版本的分派动作称为静态分派,发生在编译阶段
静态方法也是可以拥有重载版本,选择重载版本的过程也是通过静态分配完成
12.动态分配(重写):
在运行期根据实际类型确定方法执行版本的分派过程称为动态分配
动态分配分为单分派和多分派,依据其宗量(影响虚拟机选择的因素)的数量
子类重写了父类的方法,在调用时根据实际类型进行调用