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