代码改变世界

方法调用-解析

2012-12-29 23:14  fangzhao.lee  阅读(406)  评论(0编辑  收藏  举报

方法调用阶段唯一的任务就是确定被调用方法的版本(即调用哪一个方法),暂时还不涉及方法内部的具体运行过程.由于Class文件的编译过程中不包含传统编译中的连接步骤,一切方法调用在Class文件里存储的只是符号引用,而非方法在实际运行时内存布局中的入口地址.该特性为Java带来了更强大的动态扩展能力.

解析

所有方法调用中,目标方法在Class文件里都是一个常量池中的符号引用,在类加载的解析阶段,会将其中一部分符号引用转化为直接引用--此情形的前提是:方法在程序实际运行前就有一个可确定的调用版本,且这个调用版本在运行期间是不可改变的.这类方法的调用成为解析(Resolution).

在Java语言中,符合"编译期可知,运行期不可变"要求的方法主要有静态方法和私有方法两大类,前者与类型相关联,后者在外部不可访问,这两种方法都不可能通过继承或别的方式重写出其他版本,因此它们都适合在类加载阶段进行解析.

与之相对应,在Java虚拟机中提供了四条方法调用字节码指令,分别是:

invokestatic 调用静态方法

invokespecial 调用实例构造器<init>方法,私有方法和父类方法

invokevirtual 调用所有虚方法

invokeinterface 调用接口方法,会在运行时再确定一个实现此接口的对象

只要能被invokestatic和invokespecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,符合此条件的有静态方法,私有方法,实例构造器和父类方法四类.它们在类加载时即把符号引用解析为该方法的直接引用.这些方法可以称为非虚方法,与之相反,其他方法称为虚方法(除final方法外).

以下代码中,静态方法sayHello()只可能属于类型StaticResolution,没有任何手段可以覆盖或隐藏该方法:

1 public class StaticResolution {
2     public static void sayHello() {
3         System.out.println("Hello world");
4     }        
5     
6     public static void main(String[] args) {
7         StaticResolution.sayHello();
8     }
9 }

使用javap命令查看这段程序的字节码,发现的确是通过invokestatic命令调用sayHello()方法的:

public static void main(java.lang.String[]);
    Code:
     Stack=0, Locals=1, Args_size=1
     0:    invokestatic    #31; //Method sayHello:()V
     3:    return
    LineNumberTable:
     line 15: 0
     line 16: 3

虽然final方法是使用invokevirtual指令来调用的,但由于它无法被覆盖,没有其他版本,所以也无须对方法接收者进行多态选择,或者说多态选择的结果肯定是唯一的.在Java语言规范中明确说明了final方法是一种非虚方法.

 

读周志明的<深入理解Java虚拟机>一书第8.3章节, 备忘