java虚拟机8

代码执行流程梳理

  • 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)
  • 因为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开发组

    • 最好写非捕获型的

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/115070914
今日推荐