首页 > 代码库 > 关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理
关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理
JVM中相关方法的调用的指令
invokestatic
invokespecial
invokevirtual
invokeinterface
解析(resolution)与分派(dispatch)
解析
为可确定的直接引用,不会延迟到运行期再去完成。
/** * 方法静态解析演示 * * @author cxt */ public class StaticResolution { //静态方法 public static void say1() { } //final方法 public final void say2() { } //一般虚方法 public void say3() { } //重载say3 public void say3(int i) { } public static void main(String[] args) { StaticResolution.say1(); StaticResolution sr = new StaticResolution(); sr.say2(); sr.say3(); sr.say3(10); } }
// class version 51.0 (51) // access flags 0x21 public class StaticResolution { // compiled from: StaticResolution.java // access flags 0x1 public <init>() : void L0 LINENUMBER 8 L0 ALOAD 0: this INVOKESPECIAL Object.<init> () : void RETURN L1 LOCALVARIABLE this StaticResolution L0 L1 0 MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x9 public static say1() : void L0 LINENUMBER 12 L0 RETURN MAXSTACK = 0 MAXLOCALS = 0 // access flags 0x11 public final say2() : void L0 LINENUMBER 17 L0 RETURN L1 LOCALVARIABLE this StaticResolution L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 // access flags 0x1 public say3() : void L0 LINENUMBER 22 L0 RETURN L1 LOCALVARIABLE this StaticResolution L0 L1 0 MAXSTACK = 0 MAXLOCALS = 1 // access flags 0x1 public say3(int) : void L0 LINENUMBER 26 L0 RETURN L1 LOCALVARIABLE this StaticResolution L0 L1 0 LOCALVARIABLE i int L0 L1 1 MAXSTACK = 0 MAXLOCALS = 2 // access flags 0x9 public static main(String[]) : void <strong><em style="text-decoration: underline;"> </em><span style="color:#ff6666;"><del>L0 LINENUMBER 29 L0 INVOKESTATIC StaticResolution.say1 () : void L1 LINENUMBER 30 L1 NEW StaticResolution DUP INVOKESPECIAL StaticResolution.<init> () : void ASTORE 1 L2 LINENUMBER 31 L2 ALOAD 1: sr INVOKEVIRTUAL StaticResolution.say2 () : void L3 LINENUMBER 32 L3 ALOAD 1: sr INVOKEVIRTUAL StaticResolution.say3 () : void L4 LINENUMBER 33 L4 ALOAD 1: sr BIPUSH A // '\n' (LF) INVOKEVIRTUAL StaticResolution.say3 (int) : void</del></span></strong> L5 LINENUMBER 34 L5 RETURN L6 LOCALVARIABLE args String[] L0 L6 0 LOCALVARIABLE sr StaticResolution L2 L6 1 MAXSTACK = 2 MAXLOCALS = 2 }
我们看一下main方法的字节码,可知say1方法是static方法,所有它的方法调用指令为invokestatic,再者他是一个静态解析过程,我们可以从字节码清除地看出来
StaticResolution.say1 ()字样。
say2()是一个final方法,不可以重载,重写,虽然是一个invokevirtual方法但是他也是静态解析的。
say3()也是invokevirtual方法,但也是静态解析的。
say3(int i)是say3()的一个重载方法,但是重载是在编译期就确定到底要调用哪个方法,所以也是静态解析。
分派(Dispatch)
public class StaticDispatch { static abstract class Human { } static class Man extends Human { } static class Woman extends Human { } public void sayHello(Human guy) { System.out.println("hello,guy!"); } public void <span style="color:#ff0000;">sayHello(Man guy)</span> { System.out.println("hello,gentleman!"); } public void <span style="color:#ff0000;">sayHello(Woman guy)</span> { System.out.println("hello,lady!"); } public static void main(String[] args) { Human man = new Man(); Human woman = new Woman(); StaticDispatch sr = new StaticDispatch(); sr.sayHello(man); sr.sayHello(woman); } }
我们注意到两个sayHello方法的参数类型分别是Man和Woman,都继承自Human。
public static main(String[]) : void L0 LINENUMBER 32 L0 NEW StaticDispatch$Man DUP INVOKESPECIAL StaticDispatch$Man.<init> () : void ASTORE 1 L1 LINENUMBER 33 L1 NEW StaticDispatch$Woman DUP INVOKESPECIAL StaticDispatch$Woman.<init> () : void ASTORE 2 L2 LINENUMBER 34 L2 NEW StaticDispatch DUP INVOKESPECIAL StaticDispatch.<init> () : void ASTORE 3 <span style="color:#ff0000;"> L3 LINENUMBER 35 L3 ALOAD 3: sr ALOAD 1: man INVOKEVIRTUAL StaticDispatch.sayHello (StaticDispatch$Human) : void L4 LINENUMBER 36 L4 ALOAD 3: sr ALOAD 2: woman INVOKEVIRTUAL StaticDispatch.sayHello (StaticDispatch$Human) : void</span> L5 LINENUMBER 37 L5 RETURN L6 LOCALVARIABLE args String[] L0 L6 0 LOCALVARIABLE man StaticDispatch$Human L1 L6 1 LOCALVARIABLE woman StaticDispatch$Human L2 L6 2 LOCALVARIABLE sr StaticDispatch L3 L6 3 MAXSTACK = 2 MAXLOCALS = 4
对于型如Human man=new Man();其中Human我们称之为静态类型,Man我们称之为实际类型。静态类型在编译期就确定了,而实际类型要到运行时才能真正地知道是什么类型。
对于方法重载,由于方法重载是在编译器就确定到底TMD要调用哪个方法,所以重载方法的参数确定,就必须由参数的静态类型(加入参数是对象类型)来确定的。
所以TMD两个sayHello方法都会变成这样
StaticDispatch.sayHello (StaticDispatch$Human)我们注意到为静态分派,且参数的类型变为Human即静态类型。
动态分派
/** * 方法动态分派演示 * @author cxt */ public class DynamicDispatch { static abstract class Human { protected abstract void sayHello(); } static class Man extends Human { @Override protected void sayHello() { System.out.println("man say hello"); } } static class Woman extends Human { @Override protected void sayHello() { System.out.println("woman say hello"); } } public static void main(String[] args) { <span style="color:#ff0000;">Human man = new Man(); Human woman = new Woman(); man.sayHello(); woman.sayHello(); man = new Woman(); man.sayHello();</span> } }
public static main(String[]) : void L0 LINENUMBER 28 L0 NEW DynamicDispatch$Man DUP INVOKESPECIAL DynamicDispatch$Man.<init> () : void ASTORE 1 L1 LINENUMBER 29 L1 NEW DynamicDispatch$Woman DUP INVOKESPECIAL DynamicDispatch$Woman.<init> () : void ASTORE 2 <span style="color:#ff0000;"> L2 LINENUMBER 30 L2 ALOAD 1: man INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span> <span style="color:#ff0000;">L3 LINENUMBER 31 L3 ALOAD 2: woman INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span> L4 LINENUMBER 32 L4 NEW DynamicDispatch$Woman DUP INVOKESPECIAL DynamicDispatch$Woman.<init> () : void ASTORE 1: man <span style="color:#ff0000;">L5 LINENUMBER 33 L5 ALOAD 1: man INVOKEVIRTUAL DynamicDispatch$Human.sayHello () : void</span> L6 LINENUMBER 34 L6 RETURN L7 LOCALVARIABLE args String[] L0 L7 0 LOCALVARIABLE man DynamicDispatch$Human L1 L7 1 LOCALVARIABLE woman DynamicDispatch$Human L2 L7 2 MAXSTACK = 2 MAXLOCALS = 3我们注意到sayHello是一个重写方法,且调用此方法的命令都是invokevirtual DynamicDispatch,也就是动态分派的,动态分派会在运行时,确定对象的实际类型,然后再确定要去调用哪个方法,这是Java最重要的一个特性。
我们看一些 invokevirtual指令的多态执行过程。
invokevirtual指令的多态查找过程开始说起,invokevirtual指令的运行时解析过程大致分为以下几个步骤:
1)找到操作数栈顶的第一个元素所指向的对象的实际类型,记作C。
2)如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这
个方法的直接引用,查找过程结束;如果不通过,则返回java.lang.IllegalAccessError异常。
3)否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程。
4)如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
单分派与多分派
/** * 单分派、多分派演示 * @author cxt */ public class Dispatch { static class QQ {} static class _360 {} public static class Father { public void hardChoice(QQ arg) { System.out.println("father choose qq"); } public void hardChoice(_360 arg) { System.out.println("father choose 360"); } } public static class Son extends Father { public void hardChoice(QQ arg) { System.out.println("son choose qq"); } public void hardChoice(_360 arg) { System.out.println("son choose 360"); } } public static void main(String[] args) { Father father = new Father(); Father son = new Son(); father.hardChoice(new _360()); son.hardChoice(new QQ()); } }
运行结果为:
father choose 360 son choose qq
bytecode:
public static main(String[]) : void L0 LINENUMBER 34 L0 NEW Dispatch$Father DUP INVOKESPECIAL Dispatch$Father.<init> () : void ASTORE 1 L1 LINENUMBER 35 L1 NEW Dispatch$Son DUP INVOKESPECIAL Dispatch$Son.<init> () : void ASTORE 2 <span style="color:#ff0000;">L2 LINENUMBER 36 L2 ALOAD 1: father NEW Dispatch$_360 DUP INVOKESPECIAL Dispatch$_360.<init> () : void INVOKEVIRTUAL Dispatch$Father.hardChoice (Dispatch$_360) : void</span> <span style="color:#ff0000;"> L3 LINENUMBER 37 L3 ALOAD 2: son NEW Dispatch$QQ DUP INVOKESPECIAL Dispatch$QQ.<init> () : void INVOKEVIRTUAL Dispatch$Father.hardChoice (Dispatch$QQ) : void</span> L4 LINENUMBER 38 L4 RETURN L5 LOCALVARIABLE args String[] L0 L5 0 LOCALVARIABLE father Dispatch$Father L1 L5 1 LOCALVARIABLE son Dispatch$Father L2 L5 2 MAXSTACK = 3 MAXLOCALS = 3
fahter.hardChioce(XX)方法到底要调用那个是在编译期间确定的,重载嘛(静态分派),由于有两个选择到底TMD的是QQ还是360或者其他都在编译期间就确定了。因为有多个宗量(可以理解为参数类型的个数)的选择,所以Java是属于一种静态多宗量分派语言。
关于JVM中方法调用的相关指令,以及解析(Resolution)和分派(Dispatch)的解释——重载的实现原理与重写的实现原理