diff --git a/blogs/JVM/虚拟机字节码执行引擎.md b/blogs/JVM/虚拟机字节码执行引擎.md index 1d49d08..75ede7b 100644 --- a/blogs/JVM/虚拟机字节码执行引擎.md +++ b/blogs/JVM/虚拟机字节码执行引擎.md @@ -58,4 +58,31 @@ ### 方法调用 -方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。 \ No newline at end of file +方法调用并不等同于方法执行,方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程。在程序运行时,进行方法调用是最普遍、最频繁的操作,但前面已经讲过,Class 文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在 Class 文件里面存储的都只是符号引用,而不是方法在实际运行时内存布局中的入口地址(或者说直接引用)。需要在类加载期间,甚至到运行期间才能确定目标方法的直接引用。 + +#### 解析 + +所有方法调用中的目标方法在 Class 文件里面都是一个常量池中的符号引用,在类加载的解析阶段,会将其中的一部分符号引用转化为直接引用,这种解析能成立的前提是:方法在程序真正运行之前就有一个可确定的调用版本,并且这个方法的调用版本在运行期是不可改变的。换句话说,调用目标在程序代码写好、编译器进行编译时就必须确定下来。这类方法的调用称为解析。 + +在 Java 语言中符合 “编译器可知,运行期不可变” 这个要求的方法,主要包括静态方法和私有方法两大类,这两种方法各自的特点决定了它们都不可能通过继承或别的方式重写其他版本,因此它们都适合在类加载阶段进行解析。 + +与之相对应的是,在 Java 虚拟机里面提供了 5 条方法调用字节码指令,分别如下: + +* invokestatic:调用静态方法 +* invokespecial:调用实例构造器 \ 方法、私有方法和父类方法 +* invokevirtual:调用所有的虚方法 +* invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象 +* invokedynamic:先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的 4 条调用指令,分派逻辑是固化在 Java 虚拟机内部的,而 invokedynamic 指令的分派逻辑是由用户所设定的引导方法决定的 + +只要能被 invokestatic 和 invokespecial 指令调用的方法,都可以在解析阶段中确定唯一的调用版本,符合这个条件的有静态方法、私有方法、实例构造器、父类方法 4 类,它们在类加载的时候就会把符号引用解析为该方法的直接引用。这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除去 final 方法)。 + +Java 中的非虚方法除了使用 invokestatic、invokespecial 调用的方法之外,还有一种,就是被 final 修饰的方法。虽然 final 方法是使用 invokevirtual 指令来调用的,但是由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,又或者说多态选择的结果肯定是唯一的。在 Java 语言规范中明确说明了 final 方法是一种非虚方法。 + +解析调用一定是个静态的过程,在编译期间就完全确定,在类装载的解析阶段就会把涉及的符号引用全部转变为可确定的直接引用,不会延迟到运行期再去完成。而分派(Dispatch)调用则可能是静态的也可能是动态的,根据分派依据的宗量数可分为单分派和多分派。这两类分派方式的两两组合就构成了静态单分派、静态多分派、动态单分派和动态多分派。 + +#### 分派 + +分派调用过程揭示了多态性特性的一些最基本的体现,如重载和重写。 + +##### 静态分派 +