密封類正式版本
經過兩個預覽版,密封類最終确定下來了,和 Java 16 中二次預覽版在功能上沒有任何差別。
public sealed interface ConstantDesc
permits String, Integer, Float, Long, Double,
ClassDesc, MethodTypeDesc, DynamicConstantDesc { ... }
密封類用于限制類或接口隻允許指定的子類來繼承或實作,适合于不希望外部擴充功能的元件來增加限制。
switch 模式比對(預覽)
switch 也可以比對執行個體的類型了,之前 16 版本中增加的模式比對隻能在條件語句 if 中使用,現在可以用在 switch 中了。
舊版本的代碼:
if (o instanceof String) {
String s = (String)o;
... use s ...
}
16 中新增的模式比對:
if (o instanceof String s) {
... use s ...
}
switch 模式比對:
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
空值也是可以比對并進行處理的:
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
DatagramSocket 可以直接加入多點傳播
DatagramSocket 增加了 joinGroup 和 leaveGroup 方法用于加入和離開多點傳播(multicast), 這使得 DatagramSocket 可以友善的用于多點傳播應用,在此之前隻能使用 MulticastSocket 。
DatagramSocket socket = new DatagramSocket(null);
socket.setReuseAddress(true);
socket.bind(new InetSocketAddress(6789)); // 綁定位址
// 加入組 228.5.6.7
InetAddress mcastaddr = InetAddress.getByName("228.5.6.7");
InetSocketAddress group = new InetSocketAddress(mcastaddr, 0);
NetworkInterface netIf = NetworkInterface.getByName("wlan0");
socket.joinGroup(group, netIf);
byte[] msgBytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);
socket.receive(packet);
// ....
// 離開組
socket.leaveGroup(group, netIf);
增強僞随機數字生成
為所有已經存在和新的僞随機數算法,提示了統一的随機數生成接口 RandomGenerator。
RandomGenerator randomGenerator = RandomGenerator.of("L32X64MixRandom");
int randomInt = randomGenerator.nextInt();
System.out.println("randomInt = " + randomInt);
在 macos 上提供對 UserDefinedFileAttributeView 的支援
檔案系統已經在 macos 提供了擷取擴充屬性的實作,通過 UserDefinedFileAttributeView 可擷取檔案擴充屬性的視圖。
Files.getFileStore(Paths.get( " ./ " )).supportsFileAttributeView(UserDefinedFileAttributeView.class)
在之前的版本,上面的代碼在 macos 上傳回 false,下面的代碼傳回 null。
Files.getFileAttributeView(Paths.get( " / " ), UserDefinedFileAttributeView.class)
Ideal Graph Visualizer (IGV) 的現代化
Ideal Graph Visualizer (IGV) 是一個可以展現 C2 編譯器 just-in-time (JIT) 中間過程的工具。 要可看圖表,可在運作程式的時候添加參數 -XX:PrintIdealGraphLevel=level和-XX:PrintIdealGraphFile=filename 來生成 圖表檔案,然後導入 IGV 檢視。
PrintIdealGraphLevel 的各個等級含義:
- 0: no output, the default
- 1: dumps graph after parsing, before matching, and final code (also dumps graphs for failed compilations, if available)
- 2: more detail, including after loop opts
- 3: even more detail
- 4: prints graph after parsing every bytecode (very slow)
也可以不生成檔案,先啟動 IGV ,JVM 啟動後會自動連接配接,通過參數 -XX:PrintIdealGraphAddress= 和 -XX:PrintIdealGraphPort= 可以自定義 IGV 的服務位址。
我電腦上裝的 openjdk 17 ,嘗試了下,沒有成功,報了下面的錯,看樣子這個功能不是給正式版本使用的。 反正目前還用不着,沒有再深入研究。
Error: VM option 'PrintIdealGraphLevel' is notproduct and is available only in debug version of VM.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
最後這次更新的現代化内容:
- 支援運作在 JDK 15 上(IGV 基于 Netbeans IDE 實作,支援程度和 Netbeans IDE 有關,實測 jdk8 和 jdk17 都能把 IGV 跑起來)
- 更快的,基于 maven 的建構系統
- 穩定化塊形成、組移除和節點跟蹤
- 預設過濾器中的着色和節點分類更直覺
- 以更自然的行為方式快速排名節點的搜尋
javadoc 在錯誤資訊中增加來源的詳情
當 JavaDoc 在源檔案中報告錯誤時,将會顯示問題源碼的行數,并使用脫字元标(^)記在行中的具體位置,類似于編譯診斷的資訊。
javadoc 新增頁面 ”New API“,優化頁面 "Deprecated"
JavaDoc 再走可以生成一個概括一個 API 變化的摘要頁面。通過添加參數 --since 啟用, 指令會查找 @since 标簽來生成新頁面,通過 --since-label 參數可以設定新頁面的标題。 在廢棄條目的摘要頁面,條目可以按照廢棄的版本進行分組檢視。
外部函數和記憶體 API(孵化功能)
提供了一套 api 可以調用外部庫和程序,而不必使用脆弱和危險的 JNI。外部調用的的相關功能 之前的版本就有添加,到 17 仍然是孵化版本,目前仍然不成熟。
Console 字元集 api
java.io.Console 增加新方法用來取字元集的。
Console console = System.console();
if(console!=null){
System.out.println(console.charset());
}
JFR 增加反序列化事件
JFR(JDK Flight Recorder )新增加事件 jdk.Deserialization,當運作中的程式試圖對一個對象進行反序列化時觸發, 預設關閉。
針對上下文環境的反序列化過濾器
通過類 ObjectInputFilter.Config 可以設定或擷取 JVM 範圍内的反序列化過濾器,相關方法定義:
/**
* Return the JVM-wide deserialization filter factory.
*
* @return the JVM-wide serialization filter factory; non-null
*/
public static BinaryOperator<ObjectInputFilter> getSerialFilterFactory();
/**
* Set the JVM-wide deserialization filter factory.
*
* The filter factory is a function of two parameters, the current filter
* and the next filter, that returns the filter to be used for the stream.
*
* @param filterFactory the serialization filter factory to set as the
* JVM-wide filter factory; not null
*/
public static void setSerialFilterFactory(BinaryOperator<ObjectInputFilter> filterFactory);
反序列化過濾器機制可以讓你防止反序列化帶來的漏洞。如果不想設定全局的,隻是簡單的自定義,可以通過 ObjectInputStream 的方法 setObjectInputFilter 進行設定。
下面是一個 BinaryOperator<ObjectInputFilter> 實作的例子。
public class FilterInThread implements BinaryOperator<ObjectInputFilter> {
// ThreadLocal to hold the serial filter to be applied
private final ThreadLocal<ObjectInputFilter> filterThreadLocal = new ThreadLocal<>();
// Construct a FilterInThread deserialization filter factory.
public FilterInThread() {}
/**
* The filter factory, which is invoked every time a new ObjectInputStream
* is created. If a per-stream filter is already set then it returns a
* filter that combines the results of invoking each filter.
*
* @param curr the current filter on the stream
* @param next a per stream filter
* @return the selected filter
*/
public ObjectInputFilter apply(ObjectInputFilter curr, ObjectInputFilter next) {
if (curr == null) {
// Called from the OIS constructor or perhaps OIS.setObjectInputFilter with no current filter
var filter = filterThreadLocal.get();
if (filter != null) {
// Prepend a filter to assert that all classes have been Allowed or Rejected
filter = ObjectInputFilter.rejectUndecidedClass(filter);
}
if (next != null) {
// Prepend the next filter to the thread filter, if any
// Initially this is the static JVM-wide filter passed from the OIS constructor
// Append the filter to reject all UNDECIDED results
filter = ObjectInputFilter.merge(next, filter);
filter = ObjectInputFilter.rejectUndecidedClass(filter);
}
return filter;
} else {
// Called from OIS.setObjectInputFilter with a current filter and a stream-specific filter.
// The curr filter already incorporates the thread filter and static JVM-wide filter
// and rejection of undecided classes
// If there is a stream-specific filter prepend it and a filter to recheck for undecided
if (next != null) {
next = ObjectInputFilter.merge(next, curr);
next = ObjectInputFilter.rejectUndecidedClass(next);
return next;
}
return curr;
}
}
/**
* Apply the filter and invoke the runnable.
*
* @param filter the serial filter to apply to every deserialization in the thread
* @param runnable a Runnable to invoke
*/
public void doWithSerialFilter(ObjectInputFilter filter, Runnable runnable) {
var prevFilter = filterThreadLocal.get();
try {
filterThreadLocal.set(filter);
runnable.run();
} finally {
filterThreadLocal.set(prevFilter);
}
}
}
增加新的系統屬性擷取原生字元集名稱
新增加 native.encoding 來擷取系統的原生字元集名稱。
// windows 系統預設設定會輸出 GBK
System.out.println(System.getProperty("native.encoding"));
添加 java.time.InstantSource
引入新接口 java.time.InstantSource ,新接口是 java.time.Clock 的抽象,隻關注目前時刻, 不涉及時區,java.time.Clock 必須要指定時區。
InstantSource system = InstantSource.system();
// 等價于 System.currentTimeMillis()
System.out.println(system.millis());
InstantSource oneDayLater = InstantSource.offset(system, Duration.ofDays(1L));
System.out.println(oneDayLater.millis());
十六進制格式化和解析
引入工具類 java.util.HexFormat 提供了十六進制和格式化和解析。
System.out.println(HexFormat.fromHexDigits("5e"));//94
// 擷取字元在十六進制中表示的資料
System.out.println(HexFormat.fromHexDigit('A'));// 10
byte b = 94;
System.out.println(HexFormat.of().toHexDigits(b));// 5e
System.out.println(HexFormat.fromHexDigit('X'));// 異常 :not a hexadecimal digit
實驗性功能編輯器黑洞( Compiler Blackholes )
這個功能對底層的基準測試很有用,它可以防止死代碼( dead-code )被消除。
例如下面這段代碼:
public void test(int x,int y){
int z = x + y;
}
由于方法沒有傳回結果,方法内的代碼會被消除,編譯後的位元組碼檔案中并沒有方法内的内容,也就不會在運作時執行。 編譯器的優化可以提升程式的性能,但是如果你想要做基準測試,這可能不是你想要的效果,因為觀察測試的過程比程式執行的結果重要。那麼現在可以通過指令行參數啟動編譯黑洞,來避免這個優化:-XX:CompileCommand=blackhole,<method>。
JMH 已經可以自動檢查,當指令可用時,使用這個功能。JMH 是一款由 OpenJDK 開發的基準測試工具,在之前都是通過 Blackhole::consume() 方法消除死代碼,這種方式産生副作用,會産生額外的消耗。
基于 vtable 的 CHA 實作
引入一個新的類層級分析(Class Hierarchy Analysis ,簡寫 CHA)實作,以提升對抽象和預設方法的處理為特色, 改進 JIT 編譯器的内聯決策,新實作取代了原來的版本成為預設實作。
為了幫助診斷錯誤是否與新版本有關,原來的實作可以通過參數來切換:-XX:+UnlockDiagnosticVMOptions -XX:-UseVtableBasedCHA ,原本的實作會在未來删除。
新版本的實作在查詢目标方法時會依賴于 vtable 的資訊。vtable 是 c++ 中的虛拟表(virtual table), 用于查找函數,解決函數通過動态或延遲綁定的方式調用的問題。
macOS/AArch64 架構移植
Jdk 終于可以支援 Mac m1 了,從 17 版本開始,可以下載下傳官方的 macOS/AArch64 版本 JDK。 想使用更早的版本,隻能尋找第三方的 JDK 了,比如 Azul JDK。如果你使用了 Idea 來做 Java 開發, Idea 很早就支援了 m1 版本的 Mac,使用内置 JDK 即可,非常友善。
統一日志支援異步日志重新整理
為了避免線程在使用統一日志(Unified Logging)造成令人讨厭的延遲,使用者可以請求統一日志系統 使用異步操作模式,使用參數 -Xlog:async 開啟。在異步模式下,所有的日志都會以消息隊列的形式存放于一個緩沖區内, 緩沖區耗盡,則會丢棄入隊的消息。通過參數 -XX:AsyncLogBufferSize= 可設定緩沖區大小。
Keytool -genkeypair 指令支援指定簽名者
keytool 工具新增加 -signer 和 -signerkeypass 兩個參數到 -genkeypair 指令中。-genkeypair 選項指定 簽名者 PrivateKeyEntry(ketstore 中的私鑰條目) 别名,-signerkeypass 指定用于保護簽名者私鑰的密碼。 新特性主要是為了支援密鑰協商算法來生成公鑰。
SunJCE 對 AES 加密提供了 KW 和 KWP 模式
SunJCE 進一步增強,支援了 RFC 3394 和 RFC 5649,也是以增加了 KW 和 KWP 兩種塊密碼模式。
新的 SunPKCS11 配置項
SunPKCS11 provider 添加了新的配置屬性,可以更好的控制本地資源的使用。為了更好的 管理本地資源,新加選項用來控制本地引用的清理頻率以及登出後是否銷毀本地的 PKCS11 令牌。
三個新的配置項:
- destroyTokenAfterLogout ,(boolean 類型,預設 false),設定為 true ,當 java.security.AuthProvider.logout() 調用,底層的令牌對象會被銷毀,資源也會被釋放。
- cleaner.shortInterval,(integer 類型,預設 2000,機關毫秒),忙碌期間清理本地引用的周期。
- cleaner.longInterval,(integer類型, 預設 60000, 機關毫秒),非忙碌期間清理本地引用的周期。
要使用 SunPKCS11 需要先安裝 PKCS11 動态庫到本地,然後進行配置。下面是簡單的示例:
pkcs.cfg 配置:
library = C:\mypkcs11.dll
name = SunPKCS11-FooAccelerator
destroyTokenAfterLogout = true
cleaner.shortInterval = 2000
cleaner.longInterval = 60000
name 和 library 必填,library 是本地 pkcs11 的連結庫位置。
AuthProvider sunpkcs11 = (AuthProvider) Security.getProvider("SunPKCS11");
sunpkcs11.configure("C:\\pkcs.cfg");
官方參考文檔:PKCS#11 Reference Guide
javadoc 包摘要頁增加關聯的包(Related Packages)
Package Summary 中增加 Related Packages 章節,相關包的的合集是根據命名來決定的, 可能會包含以下的部分:
- 父包
- 兄弟包
- 子包
JDK 内部強封裝
将所有 JDK 内部元素強封裝,除了 critical internal APIs,critical internal APIs 如下:
- sun.misc.{Signal,SignalHandler}
- sun.misc.Unsafe (The functionality of many of the methods in this class is available via variable handles (JEP 193).)
- sun.reflect.Reflection::getCallerClass(int) (The functionality of this method is available in the stack-walking API defined by JEP 259.)
- sun.reflect.ReflectionFactory
- com.sun.nio.file.{ExtendedCopyOption,ExtendedOpenOption, ExtendedWatchEventModifier,SensitivityWatchEventModifier}
也是以 --illegal-access 選項被廢棄了,如果使用了會抛出警告,并且也不會生效。 對于已經存在的代碼,必須要使用 jdk 内部的類、方法或成員變量,仍然可以使用 --add-opens 來通路 jdk 内部 api ,讓代碼繼續運作。
其它移除功能
- 将 Telia Company's Sonera Class2 證書從 cacerts 信任庫中移除
- sun.misc.Unsafe::defineAnonymousClass 被删除,可使用 MethodHandles 相關 api 替代
- 删除 RMI Activation,從 java8 開始就已經廢棄了部分,15 中标記為 deprecated,在 17 中删除
- 移除實驗性質的 AOT 和 JIT 編譯器