代码执行流程梳理
- java代码 --> class --> JVM运行时数据区(方法区)
- 代码的执行 --> 体现在JVM中是方法的执行(class,也就是字节码)
JAVA方法的调用与虚拟机栈
启动main方法
- 启动一个程序就是通过main方法启动的,因为main方法是JVM启动的入口,通常是启动一个线程来执行main方法
方法调用的字节码指令
-
方法调用一共只有5种字节码
-
①invokestatic
-
调用静态方法
-
package ex8; /** * @author King老师 * 非虚方法的调用 **/ public class StaticResolution { public static void Hello(){ System.out.println("hello King"); } public static void main(String[] args) { StaticResolution.Hello(); // StaticResolution staticResolution = new StaticResolution(); } }
-
其中main方法的字节码如下
0 invokestatic #5 <ex8/StaticResolution.Hello> 3 return
#5需要去查看对应的常量池
#5 = Methodref #6.#28 // ex8/StaticResolution.Hello:()V
#5是一个方法引用,这个static方法在编译的时候就写到常量池中了,
-
-
②invokespecial
-
用于调用私有实例方法、构造器及 super 关键字等;
-
package ex8; /** * @author King老师 * 非虚方法的调用 **/ public class StaticResolution { public static void Hello(){ System.out.println("hello King"); } public static void main(String[] args) { // StaticResolution.Hello(); StaticResolution staticResolution = new StaticResolution(); } }
-
其中main方法字节码如下
0 new #5 <ex8/StaticResolution> 3 dup 4 invokespecial #6 <ex8/StaticResolution.<init>> 7 astore_1 8 return
-
-
③invokevirtual
- 用于调用非私有实例方法,比如 public 和 protected,大多数方法调用属于这一种;
-
④Invokeinterface
-
和上面这条指令类似,不过作用于接口类;
-
package ex8; /** * @author King老师 * 接口 **/ public interface I { default void infM(){ } void inf(); }
-
package ex8; /** * @author King老师 * 接口的调用字节码查看 **/ public class Invoke implements I { @Override public void inf() { } public static void main(String[] args) throws Exception { Invoke invoke = new Invoke(); ((I) invoke).inf(); } }
-
0 new #2 <ex8/Invoke> 3 dup 4 invokespecial #3 <ex8/Invoke.<init>> 7 astore_1 8 aload_1 9 invokeinterface #4 <ex8/I.inf> count 1 14 return
-
-
⑤invokedynamic
- 用于调用动态方法。
-
JAVA方法的调用与虚拟机栈
非虚方法
-
如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为 非虚方法。
-
invokestatic
-
invokespecial
-
-
加final修饰的方法下的invokevirtual方法,也是不可变的,也是非虚方法
虚方法
- invokevirtual和Invokeinterface,其中invokevirtual要排除掉final修饰的
- 虚方法是JVM在调用时具有动态类型,来确定调用方法的目的,所以很多时候是动态绑定的过程,而动态绑定又与分派相关
分派
- 多态有两种,重载和重写
静态分派
-
重载就是分派中的静态分派,静态分派发生在编译期
-
package ex8; /** * @author King老师 * 静态分派--方法的重载--编译阶段 */ 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 sayHello(Man guy){ System.out.println("hello,gentleman!"); } public void sayHello(Woman guy){ System.out.println("hello,lady!"); } public static void main(String[]args){ StaticDispatch sr = new StaticDispatch(); Human man = new Man(); Human woman = new Woman(); sr.sayHello(man); sr.sayHello(woman); // //实际类型变化 // Human human=new Man(); // //静态类型变化 // sr.sayHello((Man)human); // human=new Woman(); // sr.sayHello((Woman)human); } }
-
打印结果都是父方法?
-
字节码
-
0 new #7 <ex8/StaticDispatch> 3 dup 4 invokespecial #8 <ex8/StaticDispatch.<init>> 7 astore_1 8 new #9 <ex8/StaticDispatch$Man> 11 dup 12 invokespecial #10 <ex8/StaticDispatch$Man.<init>> 15 astore_2 16 new #11 <ex8/StaticDispatch$Woman> 19 dup 20 invokespecial #12 <ex8/StaticDispatch$Woman.<init>> 23 astore_3 24 aload_1 25 aload_2 26 invokevirtual #13 <ex8/StaticDispatch.sayHello> 29 aload_1 30 aload_3 31 invokevirtual #13 <ex8/StaticDispatch.sayHello> 34 return
-
都是使用的invokevirtual,是虚方法
-
动态分派
-
多见于方法的重写
-
package ex8; /** * @author King老师 * 虚方法表 **/ public class Dispatch { static class QQ{} static class WX{} public static class Father{ public void hardChoice(QQ arg){ System.out.println("father choose qq"); } public void hardChoice(WX arg){ System.out.println("father choose weixin"); } } public static class Son extends Father{ public void hardChoice(QQ arg){ System.out.println("son choose qq"); } public void hardChoice(WX arg){ System.out.println("son choose weixin"); } } public static void main(String[] args) { Father father = new Father(); Father son = new Son(); father.hardChoice(new WX()); son.hardChoice(new QQ()); } }
-
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=3, args_size=1 0: new #2 // class ex8/Dispatch$Father 3: dup 4: invokespecial #3 // Method ex8/Dispatch$Father."<init>":()V 7: astore_1 8: new #4 // class ex8/Dispatch$Son 11: dup 12: invokespecial #5 // Method ex8/Dispatch$Son."<init>":()V 15: astore_2 16: aload_1 17: new #6 // class ex8/Dispatch$WX 20: dup 21: invokespecial #7 // Method ex8/Dispatch$WX."<init>":()V 24: invokevirtual #8 // Method ex8/Dispatch$Father.hardChoice:(Lex8/Dispatch$WX;)V 27: aload_2 28: new #9 // class ex8/Dispatch$QQ 31: dup 32: invokespecial #10 // Method ex8/Dispatch$QQ."<init>":()V 35: invokevirtual #11 // Method ex8/Dispatch$Father.hardChoice:(Lex8/Dispatch$QQ;)V 38: return LineNumberTable: line 27: 0 line 28: 8 line 29: 16 line 30: 27 line 31: 38 LocalVariableTable: Start Length Slot Name Signature 0 39 0 args [Ljava/lang/String; 8 31 1 father Lex8/Dispatch$Father; 16 23 2 son Lex8/Dispatch$Father;
-
重写也是使用 invokevirtual 指令,只是这个时候具备多态性。
-
invokevirtual 指令有多态查找的机制,该指令运行时,解析过程如下:
- 找到操作数栈顶的第一个元素所指向的对象实际类型,记做 c;
- 如果在类型 c 中找到与常量中的描述符和简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法直接引用,查找过程结束,不通过则返回 java.lang.IllegalAccessError;
- 否则,按照继承关系从下往上依次对 c 的各个父类进行第二步的搜索和验证过程;
- 如果始终没找到合适的方法,则抛出 java.lang.AbstractMethodError 异常,这就是 Java 语言中方法重写的本质。
另外一点,这个时候我如果结合之前课程中讲过虚拟机栈中栈中的内容,我就知道动态链接是干嘛的:invokevirtual 可以知道方法 call()的符号引用转换是在运行时期完成的,在方法调用的时候。部分符号引用在运行期间转化为直接引用,这种转化就是 动态链接。
虚拟机动态分派的实现
动态分派JVM实现
方法表
实例
-
package ex8; /** * @author King老师 * 虚方法表 **/ public class Dispatch { static class QQ{} static class WX{} public static class Father{ public void hardChoice(QQ arg){ System.out.println("father choose qq"); } public void hardChoice(WX arg){ System.out.println("father choose weixin"); } } public static class Son extends Father{ public void hardChoice(QQ arg){ System.out.println("son choose qq"); } public void hardChoice(WX arg){ System.out.println("son choose weixin"); } } public static void main(String[] args) { Father father = new Father(); Father son = new Son(); father.hardChoice(new WX()); son.hardChoice(new QQ()); } }
-
Father方法表
-
java.lang.Object的数据类型
- clone()
- equals(Object)
- …
- wait()
-
Father的数据类型
- hardChoice(QQ)
- hardChoice(WX)
-
-
Son方法表
- java.lang.Object的数据类型
- clone()
- equals(Object)
- …
- wait()
- Son的数据类型
- hardChoice(QQ)
- hardChoice(WX)
- java.lang.Object的数据类型
-
因为invokevirtual是需要按照继承关系一个一个的找,当继承关系很长时,这样效率会很低,而方法表的存在是为了提高检索效率,
-
方法表放在方法区,方法区里面会存放每一个方法的实际入口地址
接口调用
Lambda的底层实现
Lambda表达式
-
package ex8; /** * @author King老师 * Lambda表达式字节码查看 **/ public class LambdaDemo { public static void main(String[] args) { // 箭头后面的内容就是run方法要执行的东西 Runnable r = () -> System.out.println("Hello Lambda!"); r.run(); } }
-
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=2, args_size=1 0: invokedynamic #2, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 5: astore_1 6: aload_1 7: invokeinterface #3, 1 // InterfaceMethod java/lang/Runnable.run:()V 12: return LineNumberTable: line 8: 0 line 9: 6 line 10: 12 LocalVariableTable: Start Length Slot Name Signature 0 13 0 args [Ljava/lang/String; 6 7 1 r Ljava/lang/Runnable;
-
0 invokedynamic #2 <run, BootstrapMethods #0> 5 astore_1 6 aload_1 7 invokeinterface #3 <java/lang/Runnable.run> count 1 12 return
-
lambda的实现是利用invokedynamic指令
-
而#2对应的是
- #2 = InvokeDynamic #0:#30 // #0:run:()Ljava/lang/Runnable;
-
但是通过jclasslib看到的字节码则有一个BootstrapMethods方法
-
BootstrapMethods: 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #28 ()V #29 invokestatic ex8/LambdaDemo.lambda$main$0:()V #28 ()V
-
在这里有一个很关键的东西-------方法句柄MethodHandles
官网参考(https://docs.oracle.com/javase/7/docs/api/java/lang/invoke/MethodHandles.html)
-
invokedynamic
- 调用动态方法
方法句柄(MethodHandle)
-
方法句柄有点类似于反射
-
简单的说就是方法句柄,通过这个句柄可以调用相应的方法。
-
用 MethodHandle 调用方法的流程为:
- (1) 创建 MethodType,获取指定方法的签名(出参和入参)
- (2) 在 Lookup 中查找 MethodType 的方法句柄 MethodHandle
- (3) 传入方法参数通过 MethodHandle 调用方法
MethodType
-
MethodType.methodType有很多方法,是根据传入参数,来决定是否有入参和出参的
-
MethodType methodType(Class<?> rtype)
只有出参,没有入参
-
MethodType methodType(Class<?> rtype, Class<?> ptype0)
入参是ptype0,并且只有1个,出参是rtype
-
MethodType methodType(Class<?> rtype, MethodType ptypes)
入参有多个,是ptypes,出参一样只有一个,是rtype
-
MethodHandles.Lookup
- methodType的工厂类,里面有很多方法
- findVirtual:寻找invokevirtual的方法
- …
实例
-
package ex8; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; /** * @author King老师 * 方法句柄(MethodHandle)使用案例 **/ public class MethodHandleDemo { static class Bike { String sound() { return "ding ding ding"; } } static class Animal { String sound() { return "wow wow wow"; } } static class Man extends Animal { @Override String sound() { return "ha ha ha"; } } String sound(Object o) throws Throwable { //方法句柄--工厂方法Factory MethodHandles.Lookup lookup = MethodHandles.lookup(); //方法类型表示接受的参数和返回类型(第一个参数是返回参数) MethodType methodType = MethodType.methodType(String.class); //拿到具体的MethodHandle(findVirtual相当于字节码) MethodHandle methodHandle = lookup.findVirtual(o.getClass(), "sound", methodType); String obj = (String) methodHandle.invoke(o); return obj; } public static void main(String[] args) throws Throwable { String str = new MethodHandleDemo().sound(new Bike());//每次送入的实例不一样 System.out.println(str); str = new MethodHandleDemo().sound(new Animal()); System.out.println(str); str = new MethodHandleDemo().sound(new Man()); System.out.println(str); } }
Lambda表达式的捕获与非捕获
- Lambda的表达式的捕获比非捕获效率要差
实例
-
package ex8; public class LambdaCapture { public static void main(String[] args) { repeatMessage("捕获",3); repeatMessage(); } public static void repeatMessage(String text, int count) {//捕获型 Runnable r = () -> { for (int i = 0; i < count; i++) { System.out.println(text); } }; new Thread(r).start(); } public static void repeatMessage() {//非捕获型 Runnable r = () -> { System.out.println("hello king!"); }; new Thread(r).start(); } }
-
lambda表达式的方法体中,有方法体外的非静态参数时,这种情况就称为捕获型的
Lambda表达式性能问题
-
官方文档
- https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf
-
orcle公司进行了性能比较
- 和匿名函数相比,Lambda最差都和匿名函数差不多,好的情况更好
-
Lambda开发组
- 最好写非捕获型的