JEP解讀與嘗鮮系列4 – Java 16 中對於 Project Valhalla 的鋪墊
這是 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 的定義中(參考://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: