這是 JEP 解讀與嚐鮮系列的第 4 篇,之前的文章如下:
在系列之前的第一篇文章 - JEP 解讀與嚐鮮系列 1 - Java Valhalla 與 Java Inline class 中,我介紹了 Project Valhalla 專案中的核心 Java Inline Class,總結起來其實就是 Java 中的值型別。Java 中目前只有類物件,沒有值型別的物件。普通的類物件有物件頭,因此這種物件可以用來做同步鎖,可以使用它的 wait()
notify()
等方法實現阻塞同步,同時這些物件需要在堆上面分配,通過 JVM GC 進行記憶體回收。並且這種物件的陣列,只有陣列本身是記憶體連續的,上面引用的物件並不是:
Project Valhalla 提出並設計實現了 Java 的值型別,去掉了物件頭,只儲存它其中的值。這樣減少了這種物件佔用的空間,但是也讓這種物件無法使用物件的 sychronization 同步,同時也失去了 對於wait()
notify()
這些方法的支援。同時這種物件期望是可以直接在棧上直接分配的,不用像普通物件一樣需要在堆上分配,和原始型別例如 int 一樣。同時這種物件的陣列,期望在記憶體中陣列的每個物件記憶體都是連續的:
這樣也節省了指標的儲存空間。
但是這些目前還是在設計實現中,並不是最終的實現模型,但是可以看出其中的趨勢。
為了能使 Project Valhalla 最終落地實現,我們先要對 JDK 的一些元素做相容。
JDK 中的哪些類和值型別相關
首先,最先想到的就是 Java 的原始型別對應的封裝型別,例如 java.lang.Integer
。原始型別是可以也是需要改造成 Java 值型別的,但是需要避免專案中使用了 Integer 物件的 wait()
notify()
, notifyAll()
方法,或者將這個作為 synchronization 的物件。
然後想到的就是原子類,例如 java.util.concurrent.atomic.AtomicInteger
。其實在 Java 9 之後的 JMM 模型中實現了更細粒度的訪問控制,例如:
private int locked = 0;
private static final VarHandle LOCKED;
//操作 locked 的控制程式碼
static {
try {
//初始化控制程式碼
LOCKED = MethodHandles.lookup().findVarHandle(當前類.class, "locked", int.class);
} catch (Exception e) {
throw new Error(e);
}
}
//原子操作
LOCKED.compareAndSet(this, 1, 0);
LOCKED.weakCompareAndSet(this, 1, 0);
LOCKED.weakCompareAndSetAcquire(this, 1, 0);
LOCKED.weakCompareAndSetPlain(this, 1, 0);
LOCKED.weakCompareAndSetRelease(this, 1, 0);
然後還有在 Java 11 的官方文件中提出的 Value-based Classes,參考:Java
11 Value-based Classes,Java 11 中的定義是:像是 java.util.Optional
和 java.time.LocalDateTime
這種類就是 Value-based Classes,這種類的例項:
- 本身是不可變的,雖然內部的值引用指向的是一個可變物件
- 實現了
equals
,hashCode
和toString
方法,並且基於它包含的值實現,而不是基於他的 identity (例如物件基址)並且也不是基於其他物件的狀態。 - 不會使用 identity-sensitive 的操作,例如通過
==
對比兩個例項的相等,使用預設的基於物件基址的 hashcode 實現(例如呼叫System.identityHashCode(物件)
),以及作為 synchronization 的物件 - 只通過
equals
對比物件相等,而不是==
- 沒有可訪問的建構函式,而是通過工廠方法例項化,這些方法對返回的例項的 identity 不做任何保證,即這個返回物件的地址我們無法通過對於工廠方法的傳參確定;
equals
相等的兩個物件,需要有完全相同的行為
這種 Value-based Classes 其實就與 Java 值型別的特徵非常一致。於是,從 Java 16 開始,將 Value-based Classes 的定義進行了擴充套件,並且對它們的使用進行了報警限制,提示未來這些型別,不再使用普通類實現,而是使用 Project Valhalla 的 Java 值型別實現。
JEP 390: Warnings for Value-Based Classes
在 Java 16 中,為了給 Project Valhalla 的這一特性進行鋪路,引入了一個 JEP:JEP 390: Warnings for Value-Based Classes
在最新的 Value-based Classes 的定義中(參考:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/doc-files/ValueBased.html ),將原始型別的封裝類,例如 java.lang.Integer
也納入了這一類的定義範疇。並在此基礎上,增加兩個說明:
1.非常不建議使用這一類的物件作為同步引數,例如 synchronize(obj)
,無法保證這個鎖擁有者是誰以及是否是獨佔的。
這個問題倒不是因為以後要換值型別無法同步導致的,而是容易犯這種程式設計失誤:
Integer i = 1;
for (int j = 0; j < 10; j++) {
synchronized(i) {
i++; //下次迴圈就變成另一個物件了,沒有真正按照預期鎖住
}
}
2.使用 identity 相關的操作可能未來會發生變化,所以不建議使用,例如:
- 呼叫
System.identityHashCode(物件)
獲取基於物件在堆記憶體地址實現的雜湊碼,如果 Value-based Classes 變成值型別,值型別確定在棧上分配後,這個方法目前的機制就會有問題。 - 呼叫
synchronize(obj)
同步物件,如果 Value-based Classes 變成值型別,沒有普通物件的物件頭,那麼無法使用正常的鎖膨脹同步機制,同時重量鎖 mutex 由於可能值型別物件沒有堆上位置也無法使用現有的機制實現。 - 呼叫物件的
wait()
,notify()
,notifyAll()
,由於上一條同樣的影響,這些方法呼叫可能在未來版本帶來異常。
在 Java 16 之後,如果有這些用法,就會在編譯階段有報警提醒:
Integer integer = 1;
synchronized (integer) {
}
編譯階段會提示 Attempt to synchronize on an instance of a value-based class
,如果想關閉可以增加編譯引數 -Xlint:synchronization
如果想在執行階段針對這種使用有提示或者錯誤,可以通過新增如下啟動引數實現:
-XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1
:加上這個,程式遇到這種使用,會丟擲 FATAL ERROR,同時退出 JVM-XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2
:加上這個,程式遇到這種使用,會有日誌提示:
[0.152s][info][valuebasedclasses] Synchronizing on object 0x000000069aed7788 of klass java.lang.Integer
[0.152s][info][valuebasedclasses] at com.github.hashjang.shenandoah.Main.main(Main.java:8)
[0.152s][info][valuebasedclasses] - locked <0x000000069aed7788> (a java.lang.Integer)
同樣的,由於原始型別包裝類已經屬於 Value-based Class,所以就不應該使用它的構造器而是使用 valueOf()
代替了,為了給大家修改的時間,目前僅僅是將構造器標記為 Deprecate for Removal
:
@Deprecated(since="9", forRemoval = true)
public Integer(int value) {
this.value = value;
}
如果有使用會提示 'Integer(int)' is deprecated and marked for removal
。
目前 JDK 中的未來可能會用值型別代替的 Value-based Classes
目前 JDK 中的 Value-based Classes 都帶有 jdk.internal.ValueBased
註解,或者他們的實現介面,父類帶有這個註解,包括:
java.lang
包:- 原始型別的封裝類,例如
java.lang.Integer
java.lang.Runtime.Version
類- 作業系統程式的控制程式碼
java.lang.ProcessHandle
和他的實現類java.lang.ProcessHandleImpl
- 原始型別的封裝類,例如
java.time
包下的一些時間封裝類java.util
包:- Optional 相關,例如:
java.util.Optional
,java.util.OptionalInt
,java.util.OptionalLong
,java.util.OptionalDouble
- 所有不可變集合以及底層實現的不可變元素,例如:
Set.of
的返回java.util.ImmutableCollections.AbstractImmutableSet
- Optional 相關,例如:
一點趣事兒
Java 16 的 Record 還讓我鬧了個笑話,我以為這個是 Project Valhala 的 Inline Object 已經實現了,還去 StackOverflow 問,這個 Record 為啥能有 wait() 方法,並且可以進行 synchronized 同步(因為如果是 Project Valhala 的 Inline Object 的話是沒有普通類的物件頭的,沒法用普通類物件的方法實現同步),結果。。。。。最後還是 Goetz 大佬一眼就看出我是誤會了:
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: