在以前的 JEP 嚐鮮系列中,咱們介紹了 Java Project Valhalla 以及 Java 值類型,通過 Java 14,15,16 的不斷開發優化反饋,終於 Java 16 咱們迎來了 Java 值類型的最終版設計,能夠正式在生產使用 Java 值類型相關 API 也就是 Record 這個類了。java
相關資料:git
可是,使用這個值類型 Record 替代原有的全部 Pojo 類,會遇到不少問題。這些問題包括:github
Record 要解決的問題最主要的一點就是,讓Java適應現代硬件:在 Java 語言發佈之初,一次內存訪問和一次數字計算的消耗時間是差很少的,可是如今,一次內存訪問耗時大概是一次數值計算的 200 ~ 1000 倍。從語言設計上來講,也就是間接訪問帶來的經過指針獲取的須要操做的內存,對於總體性能影響很大。編程
Java 是基於對象的語言,也就是說,Java 是一種基於指針的間接引用的語言。這個基於指針的特性,給每一個對象帶來了惟一標識性。例如判斷兩個 Object 的 ==,其實判斷的是兩個對象的內存相對映射地址是否相同,儘管兩個對象的 field 徹底同樣,他們的內存地址也不一樣。同時這個特性也給對象帶來了多態性,易變性還有鎖的特性。可是,並非全部對象都須要這種特性。bootstrap
因爲指針與間接訪問帶來了性能瓶頸,Java 準備對於不須要以上提到的特性的對象移除這些特性。因而乎, Record 出現了。微信
咱們先舉一個簡單例子,聲明一個用戶 Record。app
public record User(long id, String name, int age) {}
這樣編寫代碼以後,Record 類默認包含的元素和方法實現包括:框架
int id, String name, int age
),而且,這些元素都是 final 的。咱們來使用下這個 Record :ide
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
查看上面舉得例子的字節碼,有兩種方式,一是經過 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 插件查看,我推薦使用這種方法,查看效果以下:
這些方法的核心就是 invokedynamic:
看上去貌似是調用另一個方法,這種間接調用難道沒有性能損耗問題麼?這一點 JVM 開發者已經想到了。咱們先來來了解下 invokedynamic。
Java 最先是一種靜態類型語言,也就是說它的類型檢查的主體過程主要是在編譯期而不是運行期。爲了兼容動態類型語法,也是爲了 JVM 可以兼容動態語言(JVM 設計初衷並非只能運行 Java),在 Java 7 引入了字節碼指令 invokedynamic。這也是後來 Java 8 的拉姆達表達式以及 var 語法的實現基礎。
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();
經過字節碼能夠看出 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.ObjectMethods
的 bootstap()
方法。其核心代碼是: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: