天天看點

JDK 16 正式釋出,新特性實踐嘗鮮來啦!

新特性一覽

在開始之前,先讓我們來一起浏覽一下JDK 16版本所帶來的17個新特性吧。

▐  本文将解讀的新特性

357: OpenJDK源代碼倉庫從Mercurial遷移至Git。努力推動這一改變,将會在版本控制系統中繼資料大小、可用工具以及托管等方面展現優勢。

369: 遷移到GitHub,這個變化是基于OpenJDK源碼庫遷移至Git的,JDK 16源代碼倉庫将出現在最流行的程式員社交網站上。

386: 在x64和AArch64架構上,将JDK移植到Alpine Linux和其他使用musl作為其主要C庫的Linux發行版。Musl是 ISO C和Posix标準中描述的标準庫功能的Linux實作。Alpine Linux由于其鏡像小而被廣泛應用于雲部署、微服務以及容器環境中。Linux版本的Docker容器鏡像小于6MB。讓Java在此類設定中開箱即用地運作,并允許Tomcat、Jetty、Spring和其它流行的架構在這些環境中工作。通過使用jlink來減少Java運作時的大小,使用者可以建立一個更小的鏡像,以運作特定的應用程式。

394: instanceof操作符的模式比對,在JDK 14和JDK 15中都已預覽過,将于JDK 16最終确定。模式比對使程式中的通用邏輯(即從對象中有條件的提取元件)可以更簡潔、更安全的表達。

395: 提供Record記錄類,作為不可變資料的透明載體。

▐  其他的新特性

347: 啟用C++ 14語言功能,允許在JDK C++源代碼中使用C++ 14功能,并提供有關在HotSpot代碼中可以使用哪些功能的具體指導。

376: 将ZGC(可擴充低延遲垃圾收集器)線程堆棧處理從安全點移至并發階段。ZGC垃圾收集器旨在使HotSpot中的GC暫停和可伸縮性問題成為過去。

380: 添加Unix-Domain Socket Channels,其中Unix-Domain(AF_UNIX)套接字的支援被添加到nio.channels包中的Socket Channel和Server Socket Channel API中。

387: 彈性Metaspace功能可将未使用的HotSpot虛拟機的Class Metadata(Metaspace)占用的記憶體更迅速的傳回給作業系統,進而減少Metaspace的占用并簡化Metaspace的代碼以降低維護成本。

388: 将JDK移植到Windows/AArch64平台。

389: 孵化階段的外部連結程式API,支援靜态類型的純Java方式通路本地代碼。此計劃的目的在于通過用更進階的純Java開發模式來替換JNI(Java本機接口),以提供與C語言的互動。它的性能将會比JNI更加優越。

390: 基于值的類的警告建議:将原始包裝類指定為基于值的類,棄用其構造函數以進行移除,并提示新的棄用警告。在Java平台中對于任何基于值的類的執行個體進行同步的錯誤嘗試會予以警告。

392: 提供用于打包獨立的Java應用程式的jpackage工具。

396: 預設情況下,JDK内部結構是強封裝的,而關鍵内部API(例如misc.Unsafe)除外。此計劃的目标包括提高JDK的安全性和可維護性,并鼓勵開發人員從直接使用内部元素逐漸遷移為使用标準API,這樣開發人員和最終使用者都可以輕松地更新到 Java 的未來版本。

397: 之前在JDK 15中進行過預覽,JDK 16中二次預覽的密封類和接口限制了可以擴充或實作它們的類和接口。此計劃的目标包括允許類或接口的建立者控制負責實作它的代碼,提供比通路修飾符更聲明性的方式來限制超類的使用,并通過提供模式分析基礎來支援模式比對的未來發展。

338: 孵化階段的矢量API(JDK将配備一個孵化器子產品),jdk.incubator.vector,以表達在可支援的CPU架構上編譯為最佳硬體指令的矢量計算,以實作優于等效标量計算的性能。

393: 孵化階段的外部存儲器通路API,允許Java程式安全的通路Java堆外的外部存儲器(包括本地、持久化媒體以及托管堆存儲器)。 如上新特性前編号為JDK Enhancement Process的辨別符,詳見文末參考資料

立即嘗鮮

浏覽完17個新特性後,我都迫不及待的想嘗試一下JDK 16,以及其中一些對工程上有所幫助的特性了。那麼先通過JDK官網進行JDK 16候選版下載下傳(

http://jdk.java.net/16/

)。 由于要友善的在系統中針對多個JDK版本進行切換,可以使用jenv(

https://github.com/jenv/jenv

)。

我們把下載下傳好的JDK16路徑添加到jenv,在做如下設定即可使用。

jenv add ${JDK16_Path}
jenv global openjdk64-16      

如果一切順利,那麼檢視JDK版本時,會有類似如下資訊的傳回。

java -version
openjdk version "16"2021-03-16
OpenJDK Runtime Environment (build 16+36-2231)
OpenJDK 64-Bit Server VM (build 16+36-2231, mixed mode, sharing)      

如果你在使用較早的IDEA版本作為開發工具,那麼使用JDK 16運作程式時,可能收到如下的錯誤:

Cannot determine path to 'tools.jar' library for 16 (path/to/jdk-16) when running from IDEA, you should update to the latest version.      

這是由于JDK9對Java運作時做了重構,已删除了rt.jar、tools.jar、dt.jar以及其它各種内部JAR包。而在較早的開發工具通常對這類JAR包有依賴,通過更新IDEA可以解決。到官網擷取一個IDEA 2021.1 EAP預發版本(

https://www.jetbrains.com/zh-cn/idea/nextversion/

)來提前體驗(也可以等待2021.3的正式版本)。

新特性解讀

▐  遷移到GitHub

早在2020年9月,OpenJDK已将Github上的jdk倉庫作為JDK 16源碼的主讀取/寫入倉庫。随着JDK 16的正式釋出,這将是OpenJDK在Github上開發完成的初代JDK版本。 而促使将OpenJDK源代碼倉庫從Mercurial遷移到Git的三個主要原因:版本控制系統中繼資料,可用工具和可用托管的大小。

  • 版本控制中繼資料大小方面,轉換後的存儲庫的初始原型已顯示出版本控制中繼資料的大小顯着減少。例如,使用Git的jdk倉庫的.git目錄大約為300MB,而使用Mercurial的.hg目錄大約為1.2GB。減少中繼資料可保留本地磁盤空間并減少克隆時間,同時減少傳輸的資料。
  • 可用工具方面,與Mercurial相比,Git可用的工具更多。所有的文本編輯器都可以本地或通過插件實作Git內建。此外,幾乎所有的IDE都帶有Git內建,包括Eclipse、Visual Studio、IDEA。
  • 可用托管方面,有許多選項可用于托管Git倉庫,無論是自托管還是作為服務托管。使用外部源碼托管提供程式的原因包括性能、與開發人員進行互動的Web API的通路權限控制 以及 蓬勃發展的社群。

OpenJDK遷移到Github之後,對于Java開發者而言還是有不少的便利:

如果是在IDEA下工作與學習,clone好JDK 16源碼,

打開Project Structure (command+;),設定Project SDK為JDK 16,并設定Project language level到16。

之後就可以愉快的看JDK 16源碼了。

JDK 16 正式釋出,新特性實踐嘗鮮來啦!

▐  将JDK移植到Alpine Linux

在雲原生時代,個人了解提升效率是第一原則:

  • 更小的鏡像體積分發時會更加迅速
  • 應用程式/容器的啟動要迅速

這樣就能保障系統水準伸縮夠快、問題出現時復原處理夠快。

另外,出于降低成本考慮,更小的鏡像體積記憶體占用會更小,分發時耗用的資源也更小。 Alpine Linux就是與雲原生的提升效率原則契合的一款獨立的非商業性的通用Linux發行版。

其關注于安全性、簡單性和資源效率,圍繞musl libc和busybox建構。這使得它比傳統的GNU/Linux發行版更小。

JDK移植到Alpine Linux後,将允許Tomcat、Jetty、Spring和其它流行的架構在其中工作。使用者可以建立一個更小的鏡像,以啟動、運作特定的應用程式。 提前準備好Docker,我們先建構一個Alpine Linux鏡像,然後添加JDK 16,最後運作一個簡單的Spring Boot程式來示範一下。

▐  建構Alpine Linux鏡像

# 擷取Alpine Linux鏡像
docker pull alpine
# 運作鏡像
docker run alpine echo'Hello Alpine!'      

通過docker images指令檢視鏡像大小會發現,alpine在截止本文完成時,鏡像大小僅僅隻有5.6MB。相對于debian、ubuntu、centos等系統動則幾十甚至上百MB的鏡像來說,alpine可是真的小!

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
alpine              latest              7731472c3f2a        7 weeks ago         5.61MB      

▐  添加JDK 16

OpenJDK通過使用jlink(JEP 282:

https://openjdk.java.net/jeps/282

)來減少Java運作時的大小,我們可以從DockerHub上擷取鏡像:16-jdk-alpine(

https://hub.docker.com/_/openjdk?tab=tags&page=1&name=16-jdk-alpine&ordering=last_updated

)。或者如下Docker指令:docker pull openjdk:16-jdk-alpine

▐  運作Spring Boot

先準備一個Spring Boot的FatJar程式,可以從Spring Boot官網擷取Hello World!樣例程式(

https://spring.io/guides/gs/rest-service/

)。建立一份Dockerfile,使用openjdk:16-jdk-alpine,并添加Spring Boot程式。

FROM openjdk:16-jdk-alpine
VOLUME /tmp
ARG JAR_FILE
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]      

▐  建構并運作

# 建構鏡像,設定JAR_FILE參數指向Spring Boot程式Jar包路徑
docker build --build-argJAR_FILE=target/rest-service-0.0.1-SNAPSHOT.jar -t alpine-jdk16-app:latest . 

# 檢視鏡像
docker images

# 根據鏡像,啟動容器運作# 
-d參數 背景運作# 
-p參數 Spring Boot預設端口8080,映射到容器端口8080
docker run -d-p8080:8080 alpine-jdk16-app:latest 

# 檢視容器運作
docker ps 

# 驗證成功之後可以停止容器
docker stop${CONTAINER_ID} 

# 通路應用
curl-w'\n' http://127.0.0.1:8080/greeting?name=jdk16      
JDK 16 正式釋出,新特性實踐嘗鮮來啦!

至此,通過Alpine Linux系統帶JDK 16運作時的Spring Boot已經啟動并可以正常的通路了。

Alpine系統JDK 16鏡像大小約為321MB。相比Oracle官方的Linux版本鏡像的467MB,減少30%+。

記錄類

從JDK 14開始提供了Record記錄類的預覽特性,這一特性将成為JDK 16的一項永久性特性。Record記錄類作為不可變資料的透明載體,其是為了回應有關Java過于冗長拘謹的抱怨。此計劃的目标包括設計一個表示簡單值集合的面向對象的構造函數,幫助開發人員專注于對不可變資料的模組化而不是擴充行為,自動實作資料驅動的方法(例如 equals() 和 屬性的通路器)。

通過較新版IDEA可以建立此類型:

聲明Record記錄類後,幾乎不需要添加額外的代碼,一組隐式聲明讓其代碼書寫很簡潔:

  • 隐式聲明了屬性
  • 隐式聲明了構造器
  • 隐式聲明了equals()、hashCode()、toString()
  • 隐式聲明了屬性的通路器,通路器名稱與屬性同名
public record Point(int x, int y) {}      

Record記錄類支援Local Classes特性,那麼當需要臨時使用Record的時候,就可以非常友善的定義與使用:

List<Merchant>findTopMerchants(List<Merchant> merchants, int month) {
    // Local record
    record MerchantSales(Merchant merchant, double sales) {}
    
    // 使用MerchantSales Record類臨時包裝merchant和sales,友善做處理。
    return merchants.stream()
        .map(merchant ->new MerchantSales(merchant, computeSales(merchant, month)))
        .sorted((m1, m2) ->Double.compare(m2.sales(), m1.sales()))        
        .map(MerchantSales::merchant)
        .collect(toList());
}      

Record記錄類将可以代替Tuple、Pair等之前在JDK之外的工具庫提供的元組功能,在與下面将介紹的模式比對特性配合,可使代碼将變得非常簡潔。

▐  模式比對

從JDK 14開始引入了一種模式比對的預覽特性,這一特性也将成為JDK 16的一項永久性特性。是以雖然JDK 16是個短期版本,也不妨礙我們在未來的JDK版本中繼續使用模式比對特性。 模式比對的現階段僅限于一種模式(類型模式)和一種語言構造(instanceof),但這隻是完整特性的一部分。即便如此,我們也已經獲得了一個顯著的好處:備援的強制轉換消失了,消除了備援的代碼,使更重要的代碼得到了更清晰的關注,同時消除了隐藏bug的地方。 舉個例子:我們在開發中當需要解析對象會用到類似如下的方式

if (obj instanceofString) {
    String s = (String) obj;
    ...
}      

使用模式比對後的等價代碼:

if (obj instanceofString s) {
    // 通過使用模式比對可以直接使用s局部變量
    ...
}      

代碼看起來是不是整潔了許多。

使用instanceof擷取對象類型是一種條件提取形式,在獲得到對象類型之後,總是要将對象強制轉換為該類型。

以前在instanceof之後必須進行顯式類型轉換,這是一種繁瑣的操作,而融合這些操作的好處不僅僅是為了簡潔,它還消除了一個常見的錯誤來源:在剪切和粘貼instanceof及強制轉換代碼,容易在修改了 instanceof的類型之後忘記修改強制轉換類型,這就給了漏洞一個藏身之處。通過instanceof的模式比對消除了這個問題,我們還可以消滅所有這種類型的bug。 另一個需要經常的做此類“先檢測後強制轉換”的地方是equals方法。再來看一個例子:

publicbooleanequals(Object o) {
    if (!(o instanceof Point)) 
        returnfalse;
    Point other = (Point) o;
    return x == other.x && y == other.y;
}      
publicbooleanequals(Object o) {
    return (o instanceof Point other)
        && x == other.x && y == other.y;
}      

這段代碼起到同樣的效果,但更簡單直接,因為我們可以隻使用一個複合布爾表達式來表達一個等價的條件,而不是使用控制流語句。 模式比對的綁定變量(如上代碼例子中 obj instanceof String s的s就是一個綁定變量)除了特殊的聲明位置以外,其作用域也與"普通"局部變量有所不同。

比如我們可以這樣寫:

if (a instanceof Point p) {
    // p is in scope
    ...
} else {
    // p not in scope here
}

// p not in scope here

if (b instanceof Point p) {     // Sure!
        ...
}      

這樣特殊的作用域讓我們能夠在if-else的多分支情況下,自由的重新聲明綁定變量,也考慮未來在switch中的case也是如此便利。如:

if (x instanceofInteger num) { ... }
elseif (x instanceofLong num) { ... }
elseif (x instanceofDouble num) { ... }      

如果模式比對可以消除Java代碼中99%的強制類型轉換操作,那麼它肯定會很流行。但還不僅限于此,随着時間的推移,将會出現其他類型的模式,它們可以進行更複雜的條件提取,使用更複雜的方式來組合模式,以及提供其他可以使用模式的構造:比如switch,甚至是catch,再加上目前已永久支援的Record類以及在預覽中的密封類等相關特性,模式比對未來一定能夠大大簡化我們編寫的代碼。

尾聲

本文從JDK 16版本所帶來的17個新特性中抽取對工程工作和學習比較有幫助的幾個特性展開解讀,快速了解了這些特性。 大部分的企業或者項目還在使用JDK 8(其依然占據JDK市場的80%,絕對的主流),源于JDK 8的超豪華新特性,如函數式接口、Lambda表達式、方法引用 / 構造器引用、更強的Steam API、接口的增強、Optional、JVM中Metaspace取代PermGen空間等等。 我們也能夠看到Java為了跟上當下技術更疊的快節奏,不斷的推陳出新。

從JDK 9開始,Java版本的釋出改為每6個月一次,JDK 11是長期支援版本以及下半年将釋出的JDK 17。

JDK 9~JDK15也不乏一些重要的新特性,如

  • JDK 9 子產品系統、JShell互動式指令行
  • JDK 10 局部變量類型推斷
  • JDK 11 ZGC試用、HTTP Client API、Steam等增強
  • JDK 12 switch表達式擴充、增加基于JMH的一套微基準套件
  • JDK 13 Socket API 重構、文本塊(多行文本)
  • JDK 14 更有價值的NPE錯誤資訊、JDK 16特性的部分預覽
  • JDK 15 密封類、Record類等JDK 16特性的預覽

希望這種快速版本疊代的政策能夠讓Java保持持續的活力,能夠讓開發者使用的更高效、更健壯!

參考資料

  加入我們 

歡迎加入淘系架構團隊,團隊成員大牛雲集,有阿裡移動中間件的創始人員、Dubbo核心成員、更有一群熱愛技術,期望用技術推動業務的小夥伴。 淘系架構團隊,推進淘系(淘寶、天貓等)架構更新,緻力于為淘系、整個集團提供基礎核心能力、産品與解決方案:

  • 業務高可用的解決方案與核心能力(精細化流量管控Marconi平台:為業務提供自适應流控、隔離與熔斷的柔性高可用解決方案,站點高可用:故障自愈、多機房與異地容災與快速切流恢複
  • 新一代的業務研發模式FaaS(一站式函數研發Gaia平台)
  • 下一代網絡協定QUIC實作與落地
  • 移動中間件(API網關MTop、接入層AServer、消息/推送、配置中心等等)

期待一起參與加入淘系基礎平台的建設~ 

履歷投遞至📮:澤彬 [email protected](淘系架構-應用架構Leader)

作者:熊政(八風)

來源:淘系技術公衆号