标签:guard 编译 正是 java方法 意思 extend 条件 线程 接收
Java虚拟机规范中制定了虚拟机字节码执行引擎的概念模型,成为各种虚拟机执行引擎的统一外观(Facade)。不同的虚拟机引擎会包含两种执行模式,解释执行和编译执行。
栈帧(Stack Frame)支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈(Virtual Machine Stack)的栈元素。栈帧存储了方法的局部变量、操作数栈、动态连接和方法返回地址等信息。方法调用开始到执行完成,对应这一个帧栈在虚拟机栈里面入栈和出栈的过程。
一个线程中的方法调用链可能会很长,很多方法同时处于执行状态,但是对于执行引擎来说只有位于栈顶的栈帧才是有效的,称为当前栈帧(Current Stack Frame),与其关联的方法称之为当前方法(Current Method)。
局部变量表(Local Variable Table)变量值存储空间,用于存储方法参数和方法内部定义的局部变量。容量以变量槽(Variable Slot)为最小单位,一个Slot可以存放一个32位以内的数据类型,在64位虚拟机中一个Slot使用64位的物理内存,会使用对齐和补白的手段让Slot在外观上看起来和32位虚拟机一致。对于64位的数据类型,虚拟机会以高位对齐的方式为其分配两个连续的Slot空间,这里把long和double数据类型分割存储的做法与“long和double的非原子性协定”中把一次long和double数据类型读写分隔位两次32位读写的做法有些类似。
局部变量表中的对象是否能够被垃圾回收的根本原因取决于Slot是否还存有对象的引用。如果有一个方法,后端的代码有很耗时的操作,而前端又定义占用了大量的内存,对于实际不再使用的变量手动设置为Null能够使得其被垃圾回收,否则即使离开了作用域,但是局部变量表作为GC Roots的一部分仍然保持着对它的关联,会导致内存一直占用无法释放。
操作数栈(Operand Stack)也常称为操作栈,他是一个先入后出(Last In First Out, LIFO)栈。
在概念模型中,两个栈帧作为虚拟机的元素是完全相互独立的。但是在大多虚拟机的实现里面都会做一些优化处理,令两个栈帧出现一部分重叠,让下面栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样进行方法调用时就可以共用一部分数据,无需进行额外的参数复制传递。
Java虚拟机的解释执行引擎称为“基于栈的执行引擎”,其中的栈指的就是操作数栈。
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。Class文件的常量池中存在大量的符号引用,字节码中方法掉红指令就以常量池中指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的使用就转化为直接引用,这种转化称之为静态解析。另外一部分会在每一次运行期间转化为直接引用,这部分称之为动态连接。
一个方法开始执行后,有两种退出方式。第一种是执行引擎遇到任意一个方法返回的字节码指令,也就是正常的return,这种退出方法称之为正常完成出口(Normal Method Invocation Completion)。
另一种退出方式是在方法执行过程中遇到了异常,这个异常没有在方法体内得到处理,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方式称之为(Abrupt Method Invocation Completion)。
无论哪种退出方式,退出之后都要返回到方法被调用的位置程序才能继续执行,方法返回时需要栈帧中保存一些信息,用来帮助恢复它上层方法的执行状态。一般来说方法正常退出调用者的PC计数器的值可以作为返回地址,栈帧中可能会保存这个计数器值。异常退出时,返回地址是要通过异常处理表来确认,栈帧中一般不会保存这部分信息。
方法退出过程实际上等同于把当前栈帧出栈,因此退出时可能的操作有:恢复上层方法的局部变量表和操作数栈,把返回值(如果有)压入调用者栈帧的操作数栈,调整PC计数器的值以指向调用方法指令后面的一条指令等。
方法调用不等于方法执行,方法调用为了确定被调用方法的版本(即调用哪个方法),暂时不涉及方法内部的具体运行过程。Class文件的编译过程中存储的符号引用,而不是实际运行时内存布局中的入口地址(直接引用)。Java方法调用需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。
类加载的解析阶段,方法调用中的部分符号引用转化为直接引用,基于的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且运行期间不可改变。换句话说调用目标在代码写好,编辑器编译时就必须确定下来。这类方法的调用称之为解析(Resolution)。
Java语言符合“编译期可知,运行期不可变”这个要求的方法,主要包括静态方法和私有方法两大类,前者与类型关联,后者在外部不可被访问,两种方法各自的特点决定了他们都不可能通过继承或别的方式重写其他版本,他们都适合在类加载阶段进行解析。
与之对应的,Java虚拟机提供了5条方法调用字节码指令:
invokestatic:调用静态方法。
invokespecial:调用实例构造器的
invokevirtual:调用所有的虚方法
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象。
invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,再此之前的4条调用指令,分派逻辑时固化在Java虚拟机内部,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,在类加载的时候会把符号引用解析为该方法的直接引用,这些方法可以称为非虚方法,其他方法称为虚方法。
分派实际上解释了如”重载“和”重写“在Java虚拟机之中是如何实现的。
Java虚拟机从第一款虚拟机到JDK7之前十余年时间里,都没有改变过字节码指令集,在JDK7中添加类invokedynamic指令,这是为了实现“动态语言类型”(Dynamic Typed Language)支持进行的改进之一,也是为了JDK8可以顺利实现Lambda表达式做技术准备。
标签:guard 编译 正是 java方法 意思 extend 条件 线程 接收
原文地址:https://www.cnblogs.com/pluto4596/p/12080436.html