关于java:实战-Java-16-值类型-Record-1-Record-的默认方法使用以及基于预编译生成相关字节码的底层实现

34次阅读

共计 11838 个字符,预计需要花费 30 分钟才能阅读完成。

在之前的 JEP 尝鲜系列中,咱们介绍了 Java Project Valhalla 以及 Java 值类型,通过 Java 14,15,16 的一直开发优化反馈,终于 Java 16 咱们迎来了 Java 值类型的最终版设计,能够正式在生产应用 Java 值类型 相干 API 也就是 Record 这个类了。

相干材料:

  • Project Valhalla
  • JEP 169: Value Objects
  • JEP 218: Generics over Primitive Types
  • JEP 359: Records(Preview)
  • JEP 384: Records (Second Preview)
  • JEP 395: Records

然而,应用这个值类型 Record 代替原有的所有 Pojo 类,会遇到很多问题。这些问题包含:

  1. 因为值类型没有原来一般 Object 的对象头等信息,所以对于一些 Object 的个性是不兼容的。
  2. 咱们目前应用 Java 开发不可能不应用很多三方 jar 包,各种库。这些库中应用的 Pojo 类型并没有应用值类型。不过,不必太放心,只有这些开源库还比拟沉闷,那么肯定早晚会兼容值类型的。

Record 的产生背景

Record 要解决的问题最次要的一点就是,让 Java 适应古代硬件 :在 Java 语言公布之初 一次内存拜访和一次数字计算的耗费工夫是差不多 的,然而 当初,一次内存拜访耗时大略是一次数值计算的 200 ~ 1000 倍。从语言设计上来说,也就是 间接拜访带来的通过指针获取的须要操作的内存,对于整体性能影响很大。

Java 是基于对象的语言,也就是说,Java 是一种 基于指针的间接援用 的语言。这个基于指针的个性,给每个对象带来了惟一标识性。例如判断两个 Object 的 ==,其实判断的是两个对象的内存绝对映射地址是否雷同,只管两个对象的 field 齐全一样,他们的内存地址也不同。同时这个个性也给对象带来了 多态性 易变性 还有 的个性。然而,并不是所有对象都须要这种个性。

因为 指针与间接拜访 带来了性能瓶颈,Java 筹备对于不须要以上提到的个性的对象移除这些个性。于是乎,Record 呈现了。

疾速上手 Record 类

咱们先举一个简略例子,申明一个用户 Record。

public record User(long id, String name, int age) {}

这样编写代码之后,Record 类默认蕴含的元素和办法实现包含:

  1. record 头指定的组成元素(int id, String name, int age),并且,这些元素都是 final 的。
  2. record 默认只有一个结构器,是蕴含所有元素的结构器。
  3. record 的每个元素都有一个对应的 getter(但这种 getter 并不是 getxxx(),而是间接用变量名命名,所以应用序列化框架,DAO 框架都要留神这一点)
  4. 实现好的 hashCode(),equals(),toString() 办法(通过主动在编译阶段生成对于 hashCode(),equals(),toString() 办法实现的字节码实现)。

咱们来应用下这个 Record:

User zhx = new User(1, "zhx", 29);
User ttj = new User(2, "ttj", 25);
System.out.println(zhx.id());//1
System.out.println(zhx.name());//zhx
System.out.println(zhx.age());//29
System.out.println(zhx.equals(ttj));//false
System.out.println(zhx.toString());//User[id=1, name=zhx, age=29]
System.out.println(zhx.hashCode());//3739156

Record 的构造是如何实现的

编译后插入相干域与办法的字节码

查看下面举得例子的字节码,有两种形式,一是通过 javap -v User.class 命令查看文字版的字节码,截取重要的字节码如下所示:

// 省略文件头,文件常量池局部
{
  //public 结构器,全副属性作为参数,并给每个 Field 赋值
  public com.github.hashzhang.basetest.User(long, java.lang.String, int);
    descriptor: (JLjava/lang/String;I)V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=5, args_size=4
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Record."<init>":()V
         4: aload_0
         5: lload_1
         6: putfield      #7                  // Field id:J
         9: aload_0
        10: aload_3
        11: putfield      #13                 // Field name:Ljava/lang/String;
        14: aload_0
        15: iload         4
        17: putfield      #17                 // Field age:I
        20: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  this   Lcom/github/hashzhang/basetest/User;
            0      21     1    id   J
            0      21     3  name   Ljava/lang/String;
            0      21     4   age   I
    MethodParameters:
      Name                           Flags
      id
      name
      age

  //public final 润饰的 toString 办法
  public final java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         // 外围实现是这个 invokedynamic,咱们前面会剖析
         1: invokedynamic #21,  0             // InvokeDynamic #0:toString:(Lcom/github/hashzhang/basetest/User;)Ljava/lang/String;
         6: areturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/github/hashzhang/basetest/User;
  //public final 润饰的 hashCode 办法
  public final int hashCode();
    descriptor: ()I
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         // 外围实现是这个 invokedynamic,咱们前面会剖析
         1: invokedynamic #25,  0             // InvokeDynamic #0:hashCode:(Lcom/github/hashzhang/basetest/User;)I
         6: ireturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/github/hashzhang/basetest/User;
  //public final 润饰的 equals 办法
  public final boolean equals(java.lang.Object);
    descriptor: (Ljava/lang/Object;)Z
    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         // 外围实现是这个 invokedynamic,咱们前面会剖析
         2: invokedynamic #29,  0             // InvokeDynamic #0:equals:(Lcom/github/hashzhang/basetest/User;Ljava/lang/Object;)Z
         7: ireturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  this   Lcom/github/hashzhang/basetest/User;
            0       8     1     o   Ljava/lang/Object;
  //public 润饰的 id 的 getter
  public long id();
    descriptor: ()J
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: getfield      #7                  // Field id:J
         4: lreturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/hashzhang/basetest/User;
  //public 润饰的 name 的 getter
  public java.lang.String name();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #13                 // Field name:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/hashzhang/basetest/User;
  //public 润饰的 age 的 getter
  public int age();
    descriptor: ()I
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #17                 // Field age:I
         4: ireturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/github/hashzhang/basetest/User;
}
SourceFile: "User.java"
Record:
  long id;
    descriptor: J

  java.lang.String name;
    descriptor: Ljava/lang/String;

  int age;
    descriptor: I

// 以下是 invokedynamic 会调用的办法以及参数信息,咱们前面会剖析
BootstrapMethods:
  0: #50 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava
/lang/Object;
    Method arguments:
      #8 com/github/hashzhang/basetest/User
      #57 id;name;age
      #59 REF_getField com/github/hashzhang/basetest/User.id:J
      #60 REF_getField com/github/hashzhang/basetest/User.name:Ljava/lang/String;
      #61 REF_getField com/github/hashzhang/basetest/User.age:I
InnerClasses:
  public static final #67= #63 of #65;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

另一种是通过 IDE 的 jclasslib 插件查看,我举荐应用这种办法,查看成果如下:

主动生成的 private final field

主动生成的全属性结构器

主动生成的 public getter 办法

主动生成的 hashCode(),equals(),toString() 办法

这些办法的外围就是 invokedynamic

看上去 貌似是调用另外一个办法 ,这种间接调用 难道没有性能损耗问题么?这一点 JVM 开发者曾经想到了。咱们先来来理解下 invokedynamic

invokedynamic 产生的背景

Java 最早是一种动态类型语言,也就是说它的类型查看的主体过程次要是在编译期而不是运行期。为了兼容动静类型语法,也是为了 JVM 可能兼容动静语言(JVM 设计初衷并不是只能运行 Java),在 Java 7 引入了字节码指令 invokedynamic。这也是起初 Java 8 的拉姆达表达式以及 var 语法的实现根底。

invokedynamic 与 MethodHandle

invokedynamic 离不开对 java.lang.invoke 包的应用。这个包的次要目标是在之前单纯依附符号援用来确定调用的指标办法这种形式以外,提供一种新的动静确定指标办法的机制,称为MethodHandle

通过 MethodHandle 能够动静获取想调用的办法进行调用,和 Java Reflection 反射相似,然而为了谋求性能效率,须要用 MethodHandle,次要起因是:Reflection 仅仅是 Java 语言上补充针对反射的实现,并没有思考效率的问题,尤其是 JIT 根本无奈针对这种反射调用进行无效的优化MethodHandle 更是像是对于字节码的办法指令调用的模仿,适当应用的话 JIT 也能对于它进行优化,例如将 MethodHandle 相干办法援用申明为 static final 的:

private static final MutableCallSite callSite = new MutableCallSite(MethodType.methodType(int.class, int.class, int.class));
private static final MethodHandle invoker = callSite.dynamicInvoker();

主动生成的 toString(), hashcode(), equals() 的实现

通过字节码能够看出 incokedynamic 理论调用的是 BoostrapMethods 中的 #0 办法:

0 aload_0
1 invokedynamic #24 <hashCode, BootstrapMethods #0>
6 ireturn

Bootstap 办法表包含:

BootstrapMethods:
  // 调用的理论是 java.lang.runtime.ObjectMethods 的 boostrap 办法
  0: #50 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava
/lang/Object;
    Method arguments:
      #8 com/github/hashzhang/basetest/User
      #57 id;name;age
      #59 REF_getField com/github/hashzhang/basetest/User.id:J
      #60 REF_getField com/github/hashzhang/basetest/User.name:Ljava/lang/String;
      #61 REF_getField com/github/hashzhang/basetest/User.age:I
InnerClasses:
  // 申明 MethodHandles.Lookup 为 final,放慢调用性能,这样调用 BootstrapMethods 外面的办法能够实现近似于间接调用的性能 
  public static final #67= #63 of #65;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

从这里,咱们就能看出,实际上 toString() 调用的是 java.lang.runtime.ObjectMethodsbootstap() 办法。其外围代码是:
ObjectMethods.java

public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type,
                                   Class<?> recordClass,
                                   String names,
                                   MethodHandle... getters) throws Throwable {
        MethodType methodType;
        if (type instanceof MethodType)
            methodType = (MethodType) type;
        else {
            methodType = null;
            if (!MethodHandle.class.equals(type))
                throw new IllegalArgumentException(type.toString());
        }
        List<MethodHandle> getterList = List.of(getters);
        MethodHandle handle;
        // 依据 method 名称,解决对应的逻辑,别离对应了 equals(),hashCode(),toString() 的实现
        switch (methodName) {
            case "equals":
                if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, recordClass, Object.class)))
                    throw new IllegalArgumentException("Bad method type:" + methodType);
                handle = makeEquals(recordClass, getterList);
                return methodType != null ? new ConstantCallSite(handle) : handle;
            case "hashCode":
                if (methodType != null && !methodType.equals(MethodType.methodType(int.class, recordClass)))
                    throw new IllegalArgumentException("Bad method type:" + methodType);
                handle = makeHashCode(recordClass, getterList);
                return methodType != null ? new ConstantCallSite(handle) : handle;
            case "toString":
                if (methodType != null && !methodType.equals(MethodType.methodType(String.class, recordClass)))
                    throw new IllegalArgumentException("Bad method type:" + methodType);
                List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";"));
                if (nameList.size() != getterList.size())
                    throw new IllegalArgumentException("Name list and accessor list do not match");
                handle = makeToString(recordClass, getterList, nameList);
                return methodType != null ? new ConstantCallSite(handle) : handle;
            default:
                throw new IllegalArgumentException(methodName);
        }
    }

其中,toString() 办法 的外围实现逻辑,就要看case "toString" 这一分支了,外围逻辑是makeToString(recordClass, getterList, nameList)

private static MethodHandle makeToString(Class<?> receiverClass,
                                            // 所有的 getter 办法
                                            List<MethodHandle> getters,
                                            // 所有的 field 名称
                                            List<String> names) {assert getters.size() == names.size();
    int[] invArgs = new int[getters.size()];
    Arrays.fill(invArgs, 0);
    MethodHandle[] filters = new MethodHandle[getters.size()];
    StringBuilder sb = new StringBuilder();
    // 先拼接类名称[sb.append(receiverClass.getSimpleName()).append("[");
    for (int i=0; i<getters.size(); i++) {MethodHandle getter = getters.get(i); // (R)T
        MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String
        MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter);    // (R)String
        filters[i] = stringifyThisField;
        // 之后拼接 field 名称 = 值
        sb.append(names.get(i)).append("=%s");
        if (i != getters.size() - 1)
            sb.append(",");
    }
    sb.append(']');
    String formatString = sb.toString();
    MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString)
                                          .asCollector(String[].class, getters.size()); // (R*)String
    if (getters.size() == 0) {
        // Add back extra R
        formatter = MethodHandles.dropArguments(formatter, 0, receiverClass);
    }
    else {MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters);
        formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs);
    }
    return formatter;
}

同理,hashcode() 实现是:

private static MethodHandle makeHashCode(Class<?> receiverClass,
                                            List<MethodHandle> getters) {MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I

    // 对于每一个 field,找到对应的 hashcode 办法,取 哈希值,最初组合在一起
    for (MethodHandle getter : getters) {MethodHandle hasher = hasher(getter.type().returnType()); // (T)I
        MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter);    // (R)I
        MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I
        accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I
    }

    return accumulator;
}

同理,equals() 实现是:

private static MethodHandle makeEquals(Class<?> receiverClass,
                                          List<MethodHandle> getters) {MethodType rr = MethodType.methodType(boolean.class, receiverClass, receiverClass);
        MethodType ro = MethodType.methodType(boolean.class, receiverClass, Object.class);
        MethodHandle instanceFalse = MethodHandles.dropArguments(FALSE, 0, receiverClass, Object.class); // (RO)Z
        MethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)Z
        MethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)Z
        MethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)Z
        MethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z
        // 比照两个对象的每个 field 的 getter 获取的值是否一样,对于援用类型通过 Objects.equals 办法,对于原始类型间接通过 == 
        for (MethodHandle getter : getters) {MethodHandle equalator = equalator(getter.type().returnType()); // (TT)Z
            MethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z
            accumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr));
        }

        return MethodHandles.guardWithTest(isSameObject,
                                           instanceTrue,
                                           MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse));
    }

微信搜寻“我的编程喵”关注公众号,每日一刷,轻松晋升技术,斩获各种 offer

正文完
 0