原文链接:http://www.jianshu.com/p/56a7c4b26b14
提出问题:为什么静态方法不能动态绑定?
public class Demo1 {
public static void main(String[] args) {
A a = new B();
a.a();
}
}
class A {
public static void a() {
System.out.println("父类A的方法a");
}
}
class B extends A {
public static void a() {
System.out.println("子类A的方法a");
}
}
输出结果: 父类A的方法a
在网上找了很多例子基本说因为静态方法是类相关,和单个对象相关联,或者是静态方法没有重写.仅仅只是将父类被重写的方法隐藏,并不是覆盖.但是并不能彻底解决心中的疑惑.看到转载文章后,对jvm加载类并执行静态方法有了一个清晰的概念.
首先,jvm的运行步骤: 加载 --> 连接(验证,准备,解析) --> 初始化 --> 使用 --> 卸载
当对main方法进行run as时,首先进行对main方法所属类进行加载.跳过连接初始化,开始使用main方法
第一步: 执行A a = new B();
加载B的class文件.并对B进行初始化准备,检查B有父类A,对A的class文件进行加载,并在方法区内分配地址,形成A类的静态数据,并反射形成java.lang.Class对象存储在方法区最上层,代表A类.并能通过A对象访问到该方法区内的数据.并在堆内生成对象A..同理再生成对象B.最后再进行初始化,基于变量有意义的值.
第二步:执行a.a();
jvm提供如下调用字节码的命令
invokestatic:调用静态方法
invokespecial:调用构造方法,私有方法,父类方法
invokeinterface:调用接口方法,在运行时确定一个该接口对应的实例对象
invokedynamic:运行时动态解析真正实例,并运行该实例的方法
也就是a.a()的执行字节码肯定是通过invokedstatic该指令来执行的.该指令只会找引用对应类的静态方法.并执行.静态方法的调用到底结束.
只有重写的非静态方法才会通过invokedynamic指令来执行,并在运行时通过以下步骤来确定执行真正对象的真正方法
- 找到操作数栈的栈顶元素所指向的对象的实际类型,记为C;
- 如果C中存在描述符和简单名称都相符的方法,则进行访问权限验证,如果验证通过,则直接返回这个方法的直接引用,否则返回java.lang.IllegalAccessError异常;
- 如果C中不存在对应的方法,则按照继承关系对C的各个父类进行第2步的操作;
- 如果各个父类也没对应的方法,则返回异常;
由于动态绑定的频繁工作,基于性能的考虑,在类的连接阶段,产生一个虚的方法表.如果子类没有重写父类的非静态方法,该子类的方法将直接指向父类实例.这样在进行方法调用的时候,在运行时确定好真正的实例对象,直接通过虚方发表找到对应的方法进行调用.