java虚拟机之-即时编译,类的加载,和方法的桥接

  • jvm hostSpot内置多个即时编译器:C1,C2,Graal(java10)

    • C1 称为client编译器,面向client端它采用的优化编译比较简单
    • C2 称为server编译器,面向性能要求比较高的服务端程序,优化复杂,编译时间长,但是执行效率更高
  • 分层编译:

    • java7开始,hostspot虚拟机采用了分层编译,热点方法会首先采用C1编译,然后热点方法中的热点会采用C2编译
    • HostSpot即时编译放在额外的编译线程中执行,根据cou数量设置编译线程数量,按照分配比例1:2分配C1和C2给编译器
  • java虚拟机的boolean类型和java语言boolean类型区别

    • java语言中,boolean类型值只能是true和false,在java虚拟机中,boolean类型被映射成int类型,true->1 ,false ->0
    • 使用asmtools.jar 分析java语言和java虚拟机中boolean类型的方式:
    $ echo '
    public class Foo {
    public static void main(String[] args) {
    boolean flag = true;
    if (flag) System.out.println("Hello, Java!");
    if (flag == true) System.out.println("Hello, JVM!");
    }
    }' > Foo.java
    $ javac Foo.java
    $ java Foo
    $ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jdis.Main Foo.class > Foo.jasm.1
    $ awk 'NR==1,/iconst_1/{sub(/iconst_1/, "iconst_2")} 1' Foo.jasm.1 > Foo.jasm
    $ java -cp /path/to/asmtools.jar org.openjdk.asmtools.jasm.Main Foo.jasm
    $ java Foo
    
  • java基本类型大小

    • java虚拟机调用一个java方法会创建一个栈帧,其中供解释器使用的为解释栈帧
    • 解释栈帧分为局部变量区和字节码操作数栈,在java虚拟机中,局部变量区相当于数组,其中,long,double需要用两个数组单元存储,其他基本类型都占用一个数组单元(boolean,byte,char,short),所以在32位hotSpot上,这些类型在栈上占用4个字节,在64位上占用8个字节
    • (boolean,byte,char,short)以上情况只存在于局部的变量,不会出现存储在对中的字段
  • java类加载流程

    • 加载:是指查找字节流,并且据此创建类的过程,但是对于数组类,没有对应的字节流,是由java虚拟机直接生成的。查找字节流,需要借助类加载器来完成

      • 类加载器:

        • 启动类加载器:所有类加载器的父类
          -扩展类加载器(java9中称为平台类加载器):负责加载相对次要,但通用的类,比如jre的lib/ext目录下jar包中的
          -应用类加载器:负责加载应用程序路径下的类,应用路径指-classpath,系统变量java,class.path或环境变量CLASSPATH路径
        • 自定义类加载器:可以自定义实现类加载器,来实现特殊的加载方式,比如对class文件加密,加载时,再利用自定义的类加载器对其解密
      • 类的唯一性:类的唯一性是由类加载器实例以及类的全名一同确定的,即便是同一串字节流,经过不同的类加载器加载,会得到两个不同的类

      • 双亲委派模型:每当一个类加载器接收到加载请求时,它先将请求转发给父类加载器,该类加载器会尝试去加载

    • 链接:连接是将创建程的类,合并到java虚拟机中,使之能够执行的过程,分为三个阶段:验证,准备和解析

      • 验证:验证加载类是否能够满足java虚拟机的约束条件
      • 准备:为被加载类的静态字段分配内存
      • 解析:对于一个方法的调用,编译器会生成一个包含目标方法所在类的名字,目标方法的名字,接受参数类型以及返回值类型的符号引用,来指代所以要调用的方法,解析阶段的目的,是将这些符号引用解析程威实际引用
    • 初始化:在java中,如果直接赋值的静态字段被final修饰,并且它的类型是基本类型或字符串,那么改字段便会被java编译器标记成常量值,其初始化直接由java虚拟机完成,除此外,java编译器则会将所有的静态代码块或赋值操作放到clinit方法中,初始化时为常量值赋值,和执行clinit方法的过程

      • 类初始化条件
        • 虚拟机启动时,初始化用户指定的主类
        • 遇到new指令,初始化new指令的目标类
        • 遇到静态方法的指令时,初始化静态方法所在的类
        • 遇到访问静态字段时,初始化改静态字段所在的类
        • 子类的初始化会触发父类的初始化
        • 如果接口对了default方法,那么会触发该接口的初始化
        • 初次调用MethodHandle实例时,初始化改MethodHandle指向的方法所在的类
      public class Singleton {
      private Singleton() {}
      private static class LazyHolder {
      static final Singleton INSTANCE = new Singleton();
      static {
      System.out.println("LazyHolder.");
      }
      }
      public static Object getInstance(boolean flag) {
      if (flag) return new LazyHolder[2];
      return LazyHolder.INSTANCE;
      }
      public static void main(String[] args) {
      getInstance(true);
      System.out.println("----");
      getInstance(false);
      }
      }
      
    使用java -version:class Singleton 查看是类加载顺序,代码中新建数组会导致LazyHolder加载,但是不会去初始化
    
    
  • jvm 方法调用

    • code:
    public class TestMethod {
    
        public void invoke(Object object,Object... args){
            System.out.printf("一个object和一个可变参数\n");
        }
    
        public void invoke(String object,Object pbj,Object... args){
            System.out.printf("一个String和一个object和一个可变参数\n");
        }
    
        public static void main(String[] args){
               TestMethod testMethod=new TestMethod();
               testMethod.invoke(null,1);
               testMethod.invoke(null,1,2);
               testMethod.invoke(null,new Object[]{1});
        }
    }
    

    运行结果为:
    一个String和一个object和一个可变参数,
    一个String和一个object和一个可变参数,
    一个object和一个可变参数

    • java编译器方法选择:针对以上情况,如果java编译器在同一个阶段中找到多个适配方法,那么会选择一个最为贴切的,决定贴切程度是形参类型的继承关系。例如,第一个调用testMethod.invoke(null,1);传入null,由于String是Object的子类,因此java编译器会认为第二个方法比较贴切。

    • jvm静态绑定和动态绑定:

      • 静态绑定:由于重载方法的区分在编译阶段完成,所以java虚拟机中不存在重载,所以重载也被称为静态绑定,或者编译时多态。也就是java虚拟机静态绑定是指在解析时,能够直接识别目标方法的情况
      • 动态绑定:对于重写父类的情况则被称为动态绑定。也就是说是需要在运行过程中根据调用者的动态类型来识别目标方法的情况
    • java字节码指令

      • invokestatic:调用静态方法
      • invokespecial:用于调用私有实例方法,构造器,和使用super关键字调用弗雷德实例方法或构造器,和所实现接口的默认方法
      • invokevirtual:调用非私有实例方法
      • invokeinterface:调用接口方法
      • invokedynamic:调用动态方法
    • 调用指令的符号引用:编译过程,java编译器会暂时用符号引用来表示目标方法,符号引用存储在class文件的常量池中,如果目标方法是接口方法,那么改引用是接口符号引用,若不是接口引用,则为非接口符号引用。可以通过“javap -v xx.class” 查看类的常量池

      • 符号引用:在执行符号引用的字节码前,jvm需要解析这些符号的引用,替换为实际引用
      • 非接口符号引用:1.如果该符号引用所指向的类为C,jvm查找步骤为:
        • 1.在C中查找符号名字及描述符的方法
        • 2.如果没有找到,在C的父类中搜索,知道Object类
        • 3.如果没有找到,在C所直接实现或间接实现的接口中搜索,且改目标方法必学为非私有,非静态的。如果目标方法在间接实现的接口中,则需要满足C与该接口之间没有其他符合条件的目标方法,如果有多个符合条件的目标方法,则任意返回一个
      • 接口符号引用,如果该符号引用指向接口B,jvm查找步骤为:
        • 1.在B中查找符号名字及描述符的方法
        • 2.如果没有找到,在Object类中的公有实例方法中搜索
        • 3.如果没有找到,在B的超接口中搜索
      • 静态绑定和动态绑定:静态绑定的方法调用实际引用的是一个指向方法的指针,动态绑定的方法调用实际引用的是一个方法表的索引
    • java重新与jvm重写:java 重新与jvm重写并不一致,java编译器会生成桥接方法来弥补这种

      • 返回参数不一致重写代码:
      public class Merchant {
          public Number actionPrice(double price){
          return 0;
          }
          }
      public class NativeMerchant extends Merchant {
          @Override
          public Double actionPrice(double price) {
          return 0.0;
          }
          public static void main(String[] args){
              Merchant merchant=new NativeMerchant();
              merchant.actionPrice(0.0);
          }
          }
      
      

      两个类,在java中判断重新是根据 方法名,和形参是否一致来判断是否重写,而在jvm中则加返回参数是否一致才会判断是否重新,NativeMerchant类继承了Merchant类,重新了actionPrice方法,但是返回参数不一样,在java中是重写,但是在jvm中就不是重新,java编译器会为这个方法做桥接来弥补

      • 通过javap -v 查看字节码:

          Last modified Apr 28, 2020; size 503 bytes
          MD5 checksum 7a2445c8c2e7af4a3cb04167ec453d20
          Compiled from "NativeMerchant.java"
        public class NativeMerchant extends Merchant
          minor version: 0
          major version: 52
          flags: ACC_PUBLIC, ACC_SUPER
        Constant pool:
           #1 = Methodref          #7.#19         // Merchant."":()V
           #2 = Methodref          #20.#21        // java/lang/Double.valueOf:(D)Ljava/lang/Double;
           #3 = Class              #22            // NativeMerchant
           #4 = Methodref          #3.#19         // NativeMerchant."":()V
           #5 = Methodref          #7.#23         // Merchant.actionPrice:(D)Ljava/lang/Number;
           #6 = Methodref          #3.#24         // NativeMerchant.actionPrice:(D)Ljava/lang/Double;
           #7 = Class              #25            // Merchant
           #8 = Utf8               
           #9 = Utf8               ()V
          #10 = Utf8               Code
          #11 = Utf8               LineNumberTable
          #12 = Utf8               actionPrice
          #13 = Utf8               (D)Ljava/lang/Double;
          #14 = Utf8               main
          #15 = Utf8               ([Ljava/lang/String;)V
          #16 = Utf8               (D)Ljava/lang/Number;
          #17 = Utf8               SourceFile
          #18 = Utf8               NativeMerchant.java
          #19 = NameAndType        #8:#9          // "":()V
          #20 = Class              #26            // java/lang/Double
          #21 = NameAndType        #27:#13        // valueOf:(D)Ljava/lang/Double;
          #22 = Utf8               NativeMerchant
          #23 = NameAndType        #12:#16        // actionPrice:(D)Ljava/lang/Number;
          #24 = NameAndType        #12:#13        // actionPrice:(D)Ljava/lang/Double;
          #25 = Utf8               Merchant
          #26 = Utf8               java/lang/Double
          #27 = Utf8               valueOf
        {
          public NativeMerchant();
            descriptor: ()V
            flags: ACC_PUBLIC
            Code:
              stack=1, locals=1, args_size=1
                 0: aload_0
                 1: invokespecial #1                  // Method Merchant."":()V
                 4: return
              LineNumberTable:
                line 7: 0
        
          public java.lang.Double actionPrice(double);
            descriptor: (D)Ljava/lang/Double;
            flags: ACC_PUBLIC
            Code:
              stack=2, locals=3, args_size=2
                 0: dconst_0
                 1: invokestatic  #2                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
                 4: areturn
              LineNumberTable:
                line 10: 0
        
          public static void main(java.lang.String[]);
            descriptor: ([Ljava/lang/String;)V
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
              stack=3, locals=2, args_size=1
                 0: new           #3                  // class NativeMerchant
                 3: dup
                 4: invokespecial #4                  // Method "":()V
                 7: astore_1
                 8: aload_1
                 9: dconst_0
                10: invokevirtual #5                  // Method Merchant.actionPrice:(D)Ljava/lang/Number;
                13: pop
                14: return
              LineNumberTable:
                line 14: 0
                line 15: 8
                line 16: 14
        
          public java.lang.Number actionPrice(double);
            descriptor: (D)Ljava/lang/Number;
            flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
            Code:
              stack=3, locals=3, args_size=2
                 0: aload_0
                 1: dload_1
                 2: invokevirtual #6                  // Method actionPrice:(D)Ljava/lang/Double;
                 5: areturn
              LineNumberTable:
                line 7: 0
        }
        
        

        可以看到字节码最后ACC_BRIDGE 表示这是一个桥接方法 ACC_SYNTHETIC表示这是由java编译器生成的方法,在改方法里面,执行invokevirtual #6 调用类方法NativeMerchant.actionPrice:(D)Ljava/lang/Double;

      • 泛型导致形参类型不一致重新

        public interface Customer {
            boolean isVIP();
        }
        
        public class VipCustomer implements Customer {
            public boolean isVIP() {
                return false;
            }
            }
        public class Merchant {
            public Number actionPrice(double price,T customer){
                return 0;
                }
            }
            public class NativeMerchant extends Merchant {
                @Override
                public Double actionPrice(double price, com.zp.jvm.VipCustomer vipCustomer) {
                    return 0.0;
                }
                public static void main(String[] args){
                    Merchant merchant=new NativeMerchant();
                    merchant.actionPrice(0.0,new VipCustomer());
                }
            }
        

        反编译NativeMerchant类文件获取到自己码

                   Constant pool:
           #1 = Methodref          #9.#34         // com/zp/jvm/Merchant."":()V
           #2 = Methodref          #35.#36        // java/lang/Double.valueOf:(D)Ljava/lang/Double;
           #3 = Class              #37            // com/zp/jvm/NativeMerchant
           #4 = Methodref          #3.#34         // com/zp/jvm/NativeMerchant."":()V
           #5 = Class              #38            // com/zp/jvm/VipCustomer
           #6 = Methodref          #5.#34         // com/zp/jvm/VipCustomer."":()V
           #7 = Methodref          #9.#39         // com/zp/jvm/Merchant.actionPrice:(DLcom/zp/jvm/Customer;)Ljava/lang/Number;
           #8 = Methodref          #3.#40         // com/zp/jvm/NativeMerchant.actionPrice:(DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
           #9 = Class              #41            // com/zp/jvm/Merchant
          #10 = Utf8               
          #11 = Utf8               ()V
          #12 = Utf8               Code
          #13 = Utf8               LineNumberTable
          #14 = Utf8               LocalVariableTable
          #15 = Utf8               this
          #16 = Utf8               Lcom/zp/jvm/NativeMerchant;
          #17 = Utf8               actionPrice
          #18 = Utf8               (DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
          #19 = Utf8               price
          #20 = Utf8               D
          #21 = Utf8               vipCustomer
          #22 = Utf8               Lcom/zp/jvm/VipCustomer;
          #23 = Utf8               main
          #24 = Utf8               ([Ljava/lang/String;)V
          #25 = Utf8               args
          #26 = Utf8               [Ljava/lang/String;
          #27 = Utf8               merchant
          #28 = Utf8               Lcom/zp/jvm/Merchant;
          #29 = Utf8               (DLcom/zp/jvm/Customer;)Ljava/lang/Number;
          #30 = Utf8               Signature
          #31 = Utf8               Lcom/zp/jvm/Merchant;
          #32 = Utf8               SourceFile
          #33 = Utf8               NativeMerchant.java
          #34 = NameAndType        #10:#11        // "":()V
          #35 = Class              #42            // java/lang/Double
          #36 = NameAndType        #43:#44        // valueOf:(D)Ljava/lang/Double;
          #37 = Utf8               com/zp/jvm/NativeMerchant
          #38 = Utf8               com/zp/jvm/VipCustomer
          #39 = NameAndType        #17:#29        // actionPrice:(DLcom/zp/jvm/Customer;)Ljava/lang/Number;
          #40 = NameAndType        #17:#18        // actionPrice:(DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
          #41 = Utf8               com/zp/jvm/Merchant
          #42 = Utf8               java/lang/Double
          #43 = Utf8               valueOf
          #44 = Utf8               (D)Ljava/lang/Double;
        {
          public com.zp.jvm.NativeMerchant();
            descriptor: ()V
            flags: (0x0001) ACC_PUBLIC
            Code:
              stack=1, locals=1, args_size=1
                 0: aload_0
                 1: invokespecial #1                  // Method com/zp/jvm/Merchant."":()V
                 4: return
              LineNumberTable:
                line 9: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0       5     0  this   Lcom/zp/jvm/NativeMerchant;
        
          public java.lang.Double actionPrice(double, com.zp.jvm.VipCustomer);
            descriptor: (DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
            flags: (0x0001) ACC_PUBLIC
            Code:
              stack=2, locals=4, args_size=3
                 0: dconst_0
                 1: invokestatic  #2                  // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
                 4: areturn
              LineNumberTable:
                line 12: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0       5     0  this   Lcom/zp/jvm/NativeMerchant;
                    0       5     1 price   D
                    0       5     3 vipCustomer   Lcom/zp/jvm/VipCustomer;
        
          public static void main(java.lang.String[]);
            descriptor: ([Ljava/lang/String;)V
            flags: (0x0009) ACC_PUBLIC, ACC_STATIC
            Code:
              stack=5, locals=2, args_size=1
                 0: new           #3                  // class com/zp/jvm/NativeMerchant
                 3: dup
                 4: invokespecial #4                  // Method "":()V
                 7: astore_1
                 8: aload_1
                 9: dconst_0
                10: new           #5                  // class com/zp/jvm/VipCustomer
                13: dup
                14: invokespecial #6                  // Method com/zp/jvm/VipCustomer."":()V
                17: invokevirtual #7                  // Method com/zp/jvm/Merchant.actionPrice:(DLcom/zp/jvm/Customer;)Ljava/lang/Number;
                20: pop
                21: return
              LineNumberTable:
                line 15: 0
                line 16: 8
                line 17: 21
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0      22     0  args   [Ljava/lang/String;
                    8      14     1 merchant   Lcom/zp/jvm/Merchant;
        
          public java.lang.Number actionPrice(double, com.zp.jvm.Customer);
            descriptor: (DLcom/zp/jvm/Customer;)Ljava/lang/Number;
            flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
            Code:
              stack=4, locals=4, args_size=3
                 0: aload_0
                 1: dload_1
                 2: aload_3
                 3: checkcast     #5                  // class com/zp/jvm/VipCustomer
                 6: invokevirtual #8                  // Method actionPrice:(DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
                 9: areturn
              LineNumberTable:
                line 9: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0      10     0  this   Lcom/zp/jvm/NativeMerchant;
        }
        Signature: #31                          // Lcom/zp/jvm/Merchant;
        SourceFile: "NativeMerchant.java"
        
        
        • 可以看到最后java编译器是将VipConsumer转为Customer,其内部调用actionPrice:(DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;方法来实现桥接的
          public java.lang.Number actionPrice(double, com.zp.jvm.Customer);
            descriptor: (DLcom/zp/jvm/Customer;)Ljava/lang/Number;
            flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
            Code:
              stack=4, locals=4, args_size=3
                 0: aload_0
                 1: dload_1
                 2: aload_3
                 3: checkcast     #5                  // class com/zp/jvm/VipCustomer
                 6: invokevirtual #8                  // Method actionPrice:(DLcom/zp/jvm/VipCustomer;)Ljava/lang/Double;
                 9: areturn
              LineNumberTable:
                line 9: 0
              LocalVariableTable:
                Start  Length  Slot  Name   Signature
                    0      10     0  this   Lcom/zp/jvm/NativeMerchant;
        }
        
        

你可能感兴趣的:(jvm)