深入思考:JVM是如何进行方法调用的原创
1年前
281945
导读
1. JVM里面是如何进行方法调用的?
2. 什么是静态分派?什么是动态分派?
3. 怎么保证动态分派的执行效率?
4. 重写和重载的执行原理?
1、JVM调用指令与非虚方法
我们在 一篇图文彻底弄懂Class文件是如何被加载进JVM的 #1.2.3、解析阶段 这篇文章里面提到,我们会在解析阶段把符号引用替换为直接引用,但这里并不是转换所有的符号引用,而是静态方法,私有方法,实例构造器和父类方法。
2、分派调用
2.1、静态分派
2 3 |
StaticDispatch sd = new StaticDispatch(); sd.sayHello(man); |
sayHello(Human)
方法,而不是 sayHello(Man)
方法。
上面的Human称为静态类型,静态类型在编译期可知;
Man称为变量的实际类型,实际类型在运行期才可以确定。
sayHello(Human)
方法:
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: new #7 // class com/itzhai/jvm/executeengine/分派/StaticDispatch$Man 3: dup 4: invokespecial #8 // Method com/itzhai/jvm/executeengine/分派/StaticDispatch$Man."<init>":()V 7: astore_1 8: new #9 // class com/itzhai/jvm/executeengine/分派/StaticDispatch 11: dup 12: invokespecial #10 // Method "<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #11 // Method sayHello:(Lcom/itzhai/jvm/executeengine/分派/StaticDispatch$Human;)V 21: return ... |
sayHello
方法。 虽然这里是Man实例的引用,但是在编译期就已经确定了要执行 sayHello
的 StaticDispatch$Human
版本的方法。静态类型
来定位方法执行版本的分派动作成为静态分派。 最常见的使用场景是方法重载。
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void sayHello(char arg) { System.out.println("char"); } public void sayHello(int arg) { System.out.println("int"); } public static void main(String[] args) { StaticDispatch2 sd = new StaticDispatch2(); sd.sayHello('a'); } } |
2.2、动态分派
2 3 4 |
Human man = new Man(); man.sayHello(); } |
invokevirtual
指令去决定最终调用谁的方法。如何保证动态分派的执行效率
方法表
就包含了虚方法表(virtual Method Table)。 (在执行invokeinterface指令时,也会用到接口的方法表-Interface Method Table)。2.3、静态(编译期)多分派
-
方法所对应的静态类型(方法的接收者);
-
参数对应的静态类型(方法的参数);
方法的接收者
和 方法的参数
两个宗量来确定生成的Class文件,所以Java的静态分派属于多分派类型。2.4、动态(执行期)单分派
2 |
sp.choice(new GitHub()); |
References
欢迎关注微信公众号《Java架构杂谈》。
点赞收藏
分类: