JEP解讀與嚐鮮系列4 - Java 16 中對於 Project Valhalla 的鋪墊

乾貨滿滿張雜湊發表於2022-01-06

這是 JEP 解讀與嚐鮮系列的第 4 篇,之前的文章如下:

在系列之前的第一篇文章 - JEP 解讀與嚐鮮系列 1 - Java Valhalla 與 Java Inline class 中,我介紹了 Project Valhalla 專案中的核心 Java Inline Class,總結起來其實就是 Java 中的值型別。Java 中目前只有類物件,沒有值型別的物件。普通的類物件有物件頭,因此這種物件可以用來做同步鎖,可以使用它的 wait() notify() 等方法實現阻塞同步,同時這些物件需要在堆上面分配,通過 JVM GC 進行記憶體回收。並且這種物件的陣列,只有陣列本身是記憶體連續的,上面引用的物件並不是:

image

Project Valhalla 提出並設計實現了 Java 的值型別,去掉了物件頭,只儲存它其中的值。這樣減少了這種物件佔用的空間,但是也讓這種物件無法使用物件的 sychronization 同步,同時也失去了 對於wait() notify()這些方法的支援。同時這種物件期望是可以直接在棧上直接分配的,不用像普通物件一樣需要在堆上分配,和原始型別例如 int 一樣。同時這種物件的陣列,期望在記憶體中陣列的每個物件記憶體都是連續的:

image

這樣也節省了指標的儲存空間

但是這些目前還是在設計實現中,並不是最終的實現模型,但是可以看出其中的趨勢。

為了能使 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.Optionaljava.time.LocalDateTime 這種類就是 Value-based Classes,這種類的例項:

  • 本身是不可變的,雖然內部的值引用指向的是一個可變物件
  • 實現了 equalshashCodetoString 方法,並且基於它包含的值實現,而不是基於他的 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

一點趣事兒

Java 16 的 Record 還讓我鬧了個笑話,我以為這個是 Project Valhala 的 Inline Object 已經實現了,還去 StackOverflow 問,這個 Record 為啥能有 wait() 方法,並且可以進行 synchronized 同步(因為如果是 Project Valhala 的 Inline Object 的話是沒有普通類的物件頭的,沒法用普通類物件的方法實現同步),結果。。。。。最後還是 Goetz 大佬一眼就看出我是誤會了

image

微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer

相關文章