生产升级JDK 17 必读手册
原文點這里,查看更多優質文章
DK 17 在 2021 年 9 月 14 號正式發布了!根據發布的規劃,這次發布的 JDK 17 是一個長期維護的版本(LTS)。
Java 17 提供了數千個性能、穩定性和安全性更新,以及 14 個 JEP(JDK 增強提案),進一步改進了 Java 語言和平臺,以幫助開發人員提高工作效率。
JDK 17 包括新的語言增強、庫更新、對新 Apple (Mx CPU)計算機的支持、舊功能的刪除和棄用,并努力確保今天編寫的 Java 代碼在未來的 JDK 版本中繼續工作而不會發生變化。它還提供語言功能預覽和孵化 API,以收集 Java 社區的反饋
語言特性增強
密封的類和接口(正式版)
封閉類可以是封閉類和或者封閉接口,用來增強 Java 編程語言,防止其他類或接口擴展或實現它們。這個特性由Java 15的預覽版本晉升為正式版本。
- 密封的類和接口解釋和應用
因為我們引入了sealed class或interfaces,這些class或者interfaces只允許被指定的類或者interface進行擴展和實現。
使用修飾符sealed,您可以將一個類聲明為密封類。密封的類使用reserved關鍵字permits列出可以直接擴展它的類。子類可以是最終的,非密封的或密封的。
之前我們的代碼是這樣的。
public class Person { } //人
class Teacher extends Person { }//教師
class Worker extends Person { } ?//工人
class Student extends Person{ } //學生
但是我們現在要限制 Person類 只能被這三個類繼承,不能被其他類繼承,需要這么做。
// 添加sealed修飾符,permits后面跟上只能被繼承的子類名稱
public sealed class Person permits Teacher, Worker, Student{ } //人
// 子類可以被修飾為 final
final class Teacher extends Person { }//教師
// 子類可以被修飾為 non-sealed,此時 Worker類就成了普通類,誰都可以繼承它
non-sealed class Worker extends Person { } ?//工人
// 任何類都可以繼承Worker
class AnyClass extends Worker{}
//子類可以被修飾為 sealed,同上
sealed class Student extends Person permits MiddleSchoolStudent,GraduateStudent{ } //學生
final class MiddleSchoolStudent extends Student { } ?//中學生
final class GraduateStudent extends Student { } ?//研究生
很強很實用的一個特性,可以限制類的層次結構。
- 補充:它是由Amber項目孵化而來(會經歷兩輪以上預覽版本)
什么是Amber項目?
Amber 項目的目標是探索和孵化更小的、以生產力為導向的 Java 語言功能,這些功能已被 OpenJDK JEP 流程接受為候選 JEP。本項目由 Compiler Group 贊助。 大多數 Amber 功能在成為 Java 平臺的正式部分之前至少要經過兩輪預覽。對于給定的功能,每輪預覽和最終標準化都有單獨的 JEP。此頁面僅鏈接到某個功能的最新 JEP。此類 JEP 可能會酌情鏈接到該功能的早期 JEP。
工具庫的更新
JEP 306:恢復始終嚴格的浮點語義
Java 編程語言和 Java 虛擬機最初只有嚴格的浮點語義。從 Java 1.2 開始,默認情況下允許在這些嚴格語義中進行微小的變化,以適應當時硬件架構的限制。這些差異不再有幫助或必要,因此已被 JEP 306 刪除。
JEP 356:增強的偽隨機數生成器
為偽隨機數生成器 (PRNG) 提供新的接口類型和實現。這一變化提高了不同 PRNG 的互操作性,并使得根據需求請求算法變得容易,而不是硬編碼特定的實現。簡單而言只需要理解如下三個問題: @pdai
JDK 17之前如何生成隨機數?
- Random 類
典型的使用如下,隨機一個int值
// random int
new Random().nextInt();
?
/**
* description 獲取指定位數的隨機數
*
* @param length 1
* @return java.lang.String
*/
public static String getRandomString(int length) {
? ?String base = "abcdefghijklmnopqrstuvwxyz0123456789";
? ?Random random = new Random();
? ?StringBuilder sb = new StringBuilder();
? ?for (int i = 0; i < length; i++) {
? ? ? ?int number = random.nextInt(base.length());
? ? ? ?sb.append(base.charAt(number));
? }
? ?return sb.toString();
}
- ThreadLocalRandom 類
提供線程間獨立的隨機序列。它只有一個實例,多個線程用到這個實例,也會在線程內部各自更新狀態。它同時也是 Random 的子類,不過它幾乎把所有 Random 的方法又實現了一遍。
/**
* nextInt(bound) returns 0 <= value < bound; repeated calls produce at
* least two distinct results
*/
public void testNextIntBounded() {
? ?// sample bound space across prime number increments
? ?for (int bound = 2; bound < MAX_INT_BOUND; bound += 524959) {
? ? ? ?int f = ThreadLocalRandom.current().nextInt(bound);
? ? ? ?assertTrue(0 <= f && f < bound);
? ? ? ?int i = 0;
? ? ? ?int j;
? ? ? ?while (i < NCALLS &&
? ? ? ? ? ? ? (j = ThreadLocalRandom.current().nextInt(bound)) == f) {
? ? ? ? ? ?assertTrue(0 <= j && j < bound);
? ? ? ? ? ?++i;
? ? ? }
? ? ? ?assertTrue(i < NCALLS);
? }
}
- SplittableRandom 類
非線程安全,但可以 fork 的隨機序列實現,適用于拆分子任務的場景。
/**
* Repeated calls to nextLong produce at least two distinct results
*/
public void testNextLong() {
? ?SplittableRandom sr = new SplittableRandom();
? ?long f = sr.nextLong();
? ?int i = 0;
? ?while (i < NCALLS && sr.nextLong() == f)
? ? ? ?++i;
? ?assertTrue(i < NCALLS);
}
為什么需要增強?
- 上述幾個類實現代碼質量和接口抽象不佳
- 缺少常見的偽隨機算法
- 自定義擴展隨機數的算法只能自己去實現,缺少統一的接口
增強后是什么樣的?
代碼的優化自不必說,我們就看下新增了哪些常見的偽隨機算法
如何使用這個呢?可以使用RandomGenerator
RandomGenerator g = RandomGenerator.of("L64X128MixRandom");
JEP 382:新的macOS渲染管道
使用 Apple Metal API 為 macOS 實現 Java 2D 管道。新管道將減少 JDK 對已棄用的 Apple OpenGL API 的依賴。
目前默認情況下,這是禁用的,因此渲染仍然使用OpenGL API;要啟用metal,應用程序應通過設置系統屬性指定其使用:
-Dsun.java2d.metal=true
Metal或OpenGL的使用對應用程序是透明的,因為這是內部實現的區別,對Java API沒有影響。Metal管道需要macOS 10.14.x或更高版本。在早期版本上設置它的嘗試將被忽略。
新的平臺支持
JEP 391:支持macOS AArch64
將 JDK 移植到 macOS/AArch64 平臺。該端口將允許 Java 應用程序在新的基于 Arm 64 的 Apple Silicon 計算機上本地運行。
舊功能的刪除和棄用
JEP 398:棄用 Applet API
所有網絡瀏覽器供應商要么已取消對 Java 瀏覽器插件的支持,要么已宣布計劃這樣做。 Applet API 已于 2017 年 9 月在 Java 9 中棄用,但并未移除。
JEP 407:刪除 RMI 激活
刪除遠程方法調用 (RMI) 激活機制,同時保留 RMI 的其余部分。
JEP 410:刪除實驗性 AOT 和 JIT 編譯器
實驗性的基于 Java 的提前 (AOT) 和即時 (JIT) 編譯器是實驗性功能,并未得到廣泛采用。作為可選,它們已經從 JDK 16 中刪除。這個 JEP 從 JDK 源代碼中刪除了這些組件。
JEP 411:棄用安全管理器以進行刪除
安全管理器可以追溯到 Java 1.0。多年來,它一直不是保護客戶端 Java 代碼的主要方法,也很少用于保護服務器端代碼。在未來的版本中將其刪除將消除重大的維護負擔,并使 Java 平臺能夠向前發展。
新功能的預覽和孵化API
JEP 406:新增switch模式匹配(預覽版)
允許針對多個模式測試表達式,每個模式都有特定的操作,以便可以簡潔安全地表達復雜的面向數據的查詢。
JEP 412:外部函數和內存api (第一輪孵化)
改進了 JDK 14 和 JDK 15 中引入的孵化 API,使 Java 程序能夠與 Java 運行時之外的代碼和數據進行互操作。通過有效地調用外部函數(即 JVM 之外的代碼)和安全地訪問外部內存,這些 API 使 Java 程序能夠調用本地庫和處理本地數據,而不會像 Java 本地接口 (JNI) 那樣脆弱和復雜。這些 API 正在*項目中開發,旨在改善 Java 和非 Java 代碼之間的交互。
JEP 414:Vector API(第二輪孵化)
如下內容來源于https://xie.infoq.cn/article/8304c894c4e38318d38ceb116,作者是九叔
AVX(Advanced Vector Extensions,高級向量擴展)實際上是 x86-64 處理器上的一套 SIMD(Single Instruction Multiple Data,單指令多數據流)指令集,相對于 SISD(Single instruction, Single dat,單指令流但數據流)而言,SIMD 非常適用于 CPU 密集型場景,因為向量計算允許在同一個 CPU 時鐘周期內對多組數據批量進行數據運算,執行性能非常高效,甚至從某種程度上來看,向量運算似乎更像是一種并行任務,而非像標量計算那樣,在同一個 CPU 時鐘周期內僅允許執行一組數據運算,存在嚴重的執行效率低下問題。
隨著 Java16 的正式來臨,開發人員可以在程序中使用 Vector API 來實現各種復雜的向量計算,由 JIT 編譯器 Server Compiler(C2)在運行期將其編譯為對應的底層 AVX 指令執行。當然,在講解如何使用 Vector API 之前,我們首先來看一個簡單的標量計算程序。示例:
void scalarComputation() {
? ?var a = new float[10000000];
? ?var b = new float[10000000];
? ?// 省略數組a和b的賦值操作
? ?var c = new float[10000000];
? ?for (int i = 0; i < a.length; i++) {
? ? ? ?c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
? }
}
在上述程序示例中,循環體內每次只能執行一組浮點運算,總共需要執行約 1000 萬次才能夠獲得最終的運算結果,可想而知,這樣的執行效率必然低效。值得慶幸的是,從 Java6 的時代開始,Java 的設計者們就在 HotSpot 虛擬機中引入了一種被稱之為 SuperWord 的自動向量優化算法,該算法缺省會將循環體內的標量計算自動優化為向量計算,以此來提升數據運算時的執行效率。當然,我們可以通過虛擬機參數-XX:-UseSuperWord來顯式關閉這項優化(從實際測試結果來看,如果不開啟自動向量優化,存在約 20%~22%之間的性能下降)。
在此大家需要注意,盡管 HotSpot 缺省支持自動向量優化,但局限性仍然非常明顯,首先,JIT 編譯器 Server Compiler(C2)僅僅只會對循環體內的代碼塊做向量優化,并且這樣的優化也是極不可靠的;其次,對于一些復雜的向量運算,SuperWord 則顯得無能為力。因此,在一些特定場景下(比如:機器學習,線性代數,密碼學等),建議大家還是盡可能使用 Java16 為大家提供的 Vector API 來實現復雜的向量計算。示例:
// 定義256bit的向量浮點運算
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
? ?var i = 0;
? ?var upperBound = SPECIES.loopBound(a.length);
? ?for (; i < upperBound; i += SPECIES.length()) {
? ? ? ?var va = FloatVector.fromArray(SPECIES, a, i);
? ? ? ?var vb = FloatVector.fromArray(SPECIES, b, i);
? ? ? ?var vc = va.mul(va).
? ? ? ? ? ? ? ?add(vb.mul(vb)).
? ? ? ? ? ? ? ?neg();
? ? ? ?vc.intoArray(c, i);
? }
? ?for (; i < a.length; i++) {
? ? ? ?c[i] = (a[i] * a[i] + b[i] * b[i]) * -1.0f;
? }
}
值得注意的是,Vector API 包含在 jdk.incubator.vector 模塊中,程序中如果需要使用 Vector API 則需要在 module-info.java 文件中引入該模塊。:
module java16.test{
? ?requires jdk.incubator.vector;
}
JEP 389:外部鏈接器 API(孵化器)
該孵化器 API 提供了靜態類型、純 Java 訪問原生代碼的特性,該 API 將大大簡化綁定原生庫的原本復雜且容易出錯的過程。Java 1.1 就已通過 Java 原生接口(JNI)支持了原生方法調用,但并不好用。Java 開發人員應該能夠為特定任務綁定特定的原生庫。它還提供了外來函數支持,而無需任何中間的 JNI 粘合代碼。
JEP 393:外部存儲器訪問 API(第三次孵化)
在 Java 14 和 Java 15 中作為孵化器 API 引入的這個 API 使 Java 程序能夠安全有效地對各種外部存儲器(例如本機存儲器、持久性存儲器、托管堆存儲器等)進行操作。它提供了外部鏈接器 API 的基礎。
如下內容來源于https://xie.infoq.cn/article/8304c894c4e38318d38ceb116,作者是九叔
在實際的開發過程中,絕大多數的開發人員基本都不會直接與堆外內存打交道,但這并不代表你從未接觸過堆外內存,像大家經常使用的諸如:RocketMQ、MapDB 等中間件產品底層實現都是基于堆外存儲的,換句話說,我們幾乎每天都在間接與堆外內存打交道。那么究竟為什么需要使用到堆外內存呢?簡單來說,主要是出于以下 3 個方面的考慮:
- 減少 GC 次數和降低 Stop-the-world 時間;
- 可以擴展和使用更大的內存空間;
- 可以省去物理內存和堆內存之間的數據復制步驟。
在 Java14 之前,如果開發人員想要操作堆外內存,通常的做法就是使用 ByteBuffer 或者 Unsafe,甚至是 JNI 等方式,但無論使用哪一種方式,均無法同時有效解決安全性和高效性等 2 個問題,并且,堆外內存的釋放也是一個令人頭痛的問題。以 DirectByteBuffer 為例,該對象僅僅只是一個引用,其背后還關聯著一大段堆外內存,由于 DirectByteBuffer 對象實例仍然是存儲在堆空間內,只有當 DirectByteBuffer 對象被 GC 回收時,其背后的堆外內存才會被進一步釋放。
在此大家需要注意,程序中通過 ByteBuffer.allocateDirect()方法來申請物理內存資源所耗費的成本遠遠高于直接在 on-heap 中的操作,而且實際開發過程中還需要考慮數據結構如何設計、序列化/反序列化如何支撐等諸多難題,所以與其使用語法層面的 API 倒不如直接使用 MapDB 等開源產品來得更實惠。
如今,在堆外內存領域,我們似乎又多了一個選擇,從 Java14 開始,Java 的設計者們在語法層面為大家帶來了嶄新的 Memory Access API,極大程度上簡化了開發難度,并得以有效的解決了安全性和高效性等 2 個核心問題。示例:
// 獲取內存訪問var句柄
var handle = MemoryHandles.varHandle(char.class,
? ? ? ?ByteOrder.nativeOrder());
// 申請200字節的堆外內存
try (MemorySegment segment = MemorySegment.allocateNative(200)) {
? ?for (int i = 0; i < 25; i++) {
? ? ? ?handle.set(segment, i << 2, (char) (i + 1 + 64));
? ? ? ?System.out.println(handle.get(segment, i << 2));
? }
}
關于堆外內存段的釋放,Memory Access API 提供有顯式和隱式 2 種方式,開發人員除了可以在程序中通過 MemorySegment 的 close()方法來顯式釋放所申請的內存資源外,還可以注冊 Cleaner 清理器來實現資源的隱式釋放,后者會在 GC 確定目標內存段不再可訪問時,釋放與之關聯的堆外內存資源。
參考文章
-
https://www.oracle.com/news/announcement/oracle-releases-java-17-2021-09-14/
-
https://openjdk.java.net/projects/amber/
-
https://pdai.tech/md/java/java8up/java17.html
-
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/random/package-summary.html
總結
以上是生活随笔為你收集整理的生产升级JDK 17 必读手册的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云上攻防--云服务&&对象存储(域名接管
- 下一篇: C++ Qt开发:SqlRelation