Valhalla項(xiàng)目:了解Java史詩(shī)級(jí)重構(gòu)
譯文?譯者 | 李睿
審校 | 孫淑娟?
在Java中,除了int這樣的原語(yǔ)之外,所有的事物都是對(duì)象。事實(shí)證明,這一設(shè)置對(duì)Java產(chǎn)生了巨大的影響,這些年來(lái),這種影響越來(lái)越大。這個(gè)看似微不足道的設(shè)計(jì)決策會(huì)在集合和泛型等關(guān)鍵領(lǐng)域引起問(wèn)題。它還限制了某些性能優(yōu)化。Java語(yǔ)言重構(gòu)項(xiàng)目Valhalla旨在糾正這些問(wèn)題。Valhalla項(xiàng)目負(fù)責(zé)人Brian Goetz表示,Valhalla將“彌合原語(yǔ)類和對(duì)象之間的裂痕”。?
可以說(shuō),Valhalla項(xiàng)目是一次史詩(shī)級(jí)的重構(gòu),旨在解決自Java誕生以來(lái)一直隱藏在平臺(tái)中的技術(shù)債務(wù)。這種徹底的進(jìn)化證明了Java不僅是經(jīng)典,而且仍然處于編程語(yǔ)言設(shè)計(jì)的前沿。以下了解Valhalla項(xiàng)目的關(guān)鍵技術(shù)組件,以及為什么它們對(duì)Java的未來(lái)如此重要。 ?
Java中的性能問(wèn)題 ?
當(dāng)Java在上世紀(jì)90年代首次引入時(shí),決定所有用戶創(chuàng)建的類型都是類。只有少數(shù)原語(yǔ)類被當(dāng)作特殊類型。它們不是作為基于指針的類結(jié)構(gòu)處理的,而是直接映射到操作系統(tǒng)類型。8種原語(yǔ)類型是int、byte、short、long、float、double、boolean和char。?
直接將這些變量映射到操作系統(tǒng)對(duì)性能來(lái)說(shuō)更好,因?yàn)閿?shù)值操作在去掉對(duì)象的引用開銷時(shí)性能更好。而且,所有數(shù)據(jù)最終在程序中都解析為這8種基本類型。類只是一種結(jié)構(gòu)和組織層,它提供了更強(qiáng)大的處理基本類型的方法。另一種結(jié)構(gòu)是數(shù)組。原語(yǔ)、類和數(shù)組構(gòu)成了Java表達(dá)能力的全部范圍。 ?
但是原語(yǔ)是與類和數(shù)組不同的,程序員已經(jīng)學(xué)會(huì)了直觀地處理這些差異。例如,原語(yǔ)是按值傳遞,而對(duì)象是按引用傳遞。其中的原因相當(dāng)深刻,這歸結(jié)為身份的問(wèn)題??梢哉f(shuō),原語(yǔ)值是可替換的:intx=4是整數(shù)4,無(wú)論它出現(xiàn)在哪里。可以在equals()vs==中看到這種區(qū)別,前者測(cè)試對(duì)象的值等價(jià)性,后者測(cè)試對(duì)象的同一性。如果兩個(gè)引用在內(nèi)存中共享相同的空間,它們滿足==,這意味著它們是同一個(gè)對(duì)象。任何設(shè)置為4的int類型也滿足==,而int類型根本不支持.equals()。 ?
Java虛擬機(jī)(JVM)可以利用處理原語(yǔ)的方式來(lái)優(yōu)化存儲(chǔ)、檢索和操作原語(yǔ)的方式。特別是,如果平臺(tái)確定一個(gè)變量沒(méi)有改變(也就是說(shuō),它是常數(shù)或不可變的),那么就可以對(duì)其進(jìn)行優(yōu)化。 ?
相比之下,對(duì)象則不適合這種優(yōu)化,因?yàn)樗鼈冇幸粋€(gè)身份。作為類的實(shí)例,對(duì)象所保存的數(shù)據(jù)既可以是原語(yǔ),也可以是其他類。對(duì)象本身用一個(gè)指針句柄來(lái)尋址。這就創(chuàng)建了一個(gè)引用網(wǎng)絡(luò):對(duì)象圖。每當(dāng)某個(gè)值被更改時(shí)(或者即使它可能被更改),JVM都必須維護(hù)對(duì)象的最終記錄以供參考。需要引用對(duì)象是一些性能優(yōu)化的障礙。?
性能上的困難還不止于此。對(duì)象作為引用桶的特性意味著它們以一種非常松散的方式存在于內(nèi)存中。Fluffy是一個(gè)技術(shù)術(shù)語(yǔ),用來(lái)描述JVM無(wú)法壓縮對(duì)象以最小化其內(nèi)存占用這一事實(shí)。當(dāng)一個(gè)對(duì)象有對(duì)另一個(gè)對(duì)象的引用作為其組成的一部分時(shí),JVM被迫維護(hù)該指針關(guān)系。(在某些情況下,巧妙的優(yōu)化可以幫助確定嵌套引用是特定實(shí)體的唯一句柄。) ?
在他撰寫的一篇博客文章中,Goetz使用了一系列點(diǎn)來(lái)說(shuō)明引用的非密集性,可以使用類。假設(shè)有一個(gè)Landmark類,它有一個(gè)名稱和一個(gè)地理位置字段。這意味著一個(gè)像圖1所示的內(nèi)存結(jié)構(gòu): ?

圖1 Java對(duì)象的“蓬松”內(nèi)存占用
想要實(shí)現(xiàn)的是在適當(dāng)?shù)臅r(shí)候保存對(duì)象的能力,如圖2所示:?

圖2. 內(nèi)存中密集的對(duì)象?
這就是早期設(shè)計(jì)決策對(duì)Java平臺(tái)的性能挑戰(zhàn)的概述。以下考慮這些決策如何在三個(gè)關(guān)鍵領(lǐng)域影響性能。 ?
問(wèn)題1:方法調(diào)用和傳遞值 ?
內(nèi)存中對(duì)象的默認(rèn)結(jié)構(gòu)對(duì)于內(nèi)存和緩存來(lái)說(shuō)都是低效的。此外,還有機(jī)會(huì)在方法調(diào)用約定方面取得進(jìn)展。能夠?qū)粗嫡{(diào)用參數(shù)傳遞給具有類語(yǔ)法的方法(在適當(dāng)?shù)那闆r下)將帶來(lái)更高性能和好處。?
問(wèn)題2:箱子和自動(dòng)裝箱?
除了低效率之外,原語(yǔ)和類之間的區(qū)別還帶來(lái)了語(yǔ)言級(jí)的困難。創(chuàng)建像Integer和Long這樣的原語(yǔ)“箱”(以及自動(dòng)裝箱)是為了減輕這種區(qū)別所帶來(lái)的問(wèn)題。但是,它并不能真正解決這些問(wèn)題,而且它給開發(fā)人員和機(jī)器都帶來(lái)了一定程度的開銷。作為開發(fā)人員,必須了解并記住int和Integer(以及ArrayList<Integer>, int[], Integer[],以及缺少ArrayList<int>)之間的區(qū)別。與此同時(shí),機(jī)器必須在兩者之間進(jìn)行轉(zhuǎn)換。?
在某種程度上,裝箱模糊了這些實(shí)體如何工作的底層細(xì)微差別,因此很難同時(shí)了解類語(yǔ)法的強(qiáng)大功能和原語(yǔ)的性能。?
問(wèn)題3:泛型和流?
在泛型中,所有這些考慮因素都非常重要。泛型旨在使跨功能的泛化更容易、更顯式,但這組非對(duì)象變量(原語(yǔ))的挑剔存在只會(huì)導(dǎo)致它崩潰。<int>不存在,因?yàn)閕nt根本不是一個(gè)類,它不是對(duì)象的后代。?
這個(gè)問(wèn)題在集合和流等庫(kù)中也有體現(xiàn),通過(guò)提供IntStream和其他非泛型變體,泛型庫(kù)函數(shù)的理想被迫處理int與Integer、long與long等現(xiàn)實(shí)。?
Valhalla的解決方案:值類和原語(yǔ)類?
Valhalla計(jì)劃從根本上解決這些問(wèn)題。第一個(gè)也是最基本的概念是值類。這里的思想是,可以定義一個(gè)類,它具有類的所有優(yōu)點(diǎn),比如具有方法并能夠?qū)崿F(xiàn)泛型,但沒(méi)有標(biāo)識(shí)。在實(shí)踐中,這意味著類是不可變的,不能是布局多態(tài)的(其中超類可以通過(guò)抽象屬性對(duì)子類進(jìn)行操作)。?
值類為人們提供了一種清晰而明確的方式來(lái)獲得所追求的性能特征,同時(shí)仍然可以訪問(wèn)類語(yǔ)法和獲得行為的好處。這意味著庫(kù)構(gòu)建者也可以使用它們,從而改進(jìn)API設(shè)計(jì)。?
更進(jìn)一步的是原語(yǔ)類,它類似于一個(gè)更極端的值類。本質(zhì)上,原語(yǔ)類是一個(gè)真正的原語(yǔ)變量的薄包裝器,但帶有類方法。這有點(diǎn)像自定義的流線型原語(yǔ)箱。改進(jìn)在于使裝箱系統(tǒng)更加顯式和可擴(kuò)展。此外,由原語(yǔ)類包裝的原語(yǔ)值保留了原語(yǔ)的性能特征(沒(méi)有底層的裝箱和拆箱)。因此,原語(yǔ)類可以在任何類可以存在的地方使用——例如,在Object[]數(shù)組中?;绢愋筒荒転榭?它們不能被設(shè)置為空)。?
一般來(lái)說(shuō),Valhalla項(xiàng)目使原語(yǔ)和用戶定義類型更加接近。這為開發(fā)人員在純?cè)Z(yǔ)和對(duì)象之間提供了更多的選擇,并使權(quán)衡變得明確。它還使這些操作總體上更加一致。特別是,新的原語(yǔ)系統(tǒng)將使原語(yǔ)和對(duì)象的工作方式、它們的裝箱方式以及如何添加新的原語(yǔ)和對(duì)象變得更加流暢。?
Java的語(yǔ)法將如何變化?
Valhalla已經(jīng)看到了一些不同的語(yǔ)法建議,但現(xiàn)在項(xiàng)目有了明確的形式和方向。兩個(gè)新的關(guān)鍵字修改了類關(guān)鍵字:value和primitive。用value class語(yǔ)法聲明的類將放棄其標(biāo)識(shí),并在這一過(guò)程中獲得性能改進(jìn)。除了可變性和多態(tài)限制外,對(duì)類的大部分期望仍然適用,并且此類類可以完全參與泛型代碼(例如object[]或ArrayList<T>)。值類默認(rèn)為null。?
原語(yǔ)類語(yǔ)法創(chuàng)建的類比傳統(tǒng)對(duì)象和傳統(tǒng)原語(yǔ)更進(jìn)一步。這些類默認(rèn)為字段的基礎(chǔ)值(int為0,double為0.0,等等),并且不能為空。原語(yǔ)類在優(yōu)化方面獲得最多,在特性方面犧牲最多。原語(yǔ)類最終將被用于對(duì)平臺(tái)中的所有原語(yǔ)建模,這意味著用戶和庫(kù)定義的原語(yǔ)添加將與內(nèi)置組件參與到同一個(gè)系統(tǒng)中。?
IdentityObject和ValueObject?
IdentityObject和ValueObject是Valhalla項(xiàng)目中引入的兩個(gè)新接口。這將允許運(yùn)行時(shí)確定正在處理的類的類型。 ?
對(duì)于有經(jīng)驗(yàn)的Java開發(fā)人員來(lái)說(shuō),最根本的語(yǔ)法更改可能是增加了.ref成員。所有類型現(xiàn)在都有V.ref()字段。這個(gè)字段的操作類似于基本類型上的box,所以是int.ref類似于用Integer包裝int。普通類將解析.ref到它們的引用??傮w效果是提供了一種一致的方式來(lái)請(qǐng)求變量的引用,而不管變量的類型是什么。這也會(huì)導(dǎo)致所有Java數(shù)組都是“協(xié)變的”,也就是說(shuō),它們都起源于Object[]。因此,int[]現(xiàn)在來(lái)自O(shè)bject[],可以在任何需要的地方使用。 ?
結(jié)論?
值類和原語(yǔ)類將對(duì)Java及其生態(tài)系統(tǒng)產(chǎn)生重大影響。當(dāng)前的路線圖計(jì)劃首先引入值類,然后是原語(yǔ)類。接下來(lái)將遷移現(xiàn)有的原語(yǔ)裝箱類(如Integer)以使用新的原語(yǔ)類。有了這些特性,下一個(gè)被稱為通用泛型的特性將允許原語(yǔ)類直接與泛型一起使用,從而消除API中重用的許多復(fù)雜性。最后,專門的泛型(允許T擴(kuò)展Foo的所有表達(dá)能力)將與原語(yǔ)類集成。 ?
Valhalla項(xiàng)目及其組成項(xiàng)目仍處于設(shè)計(jì)階段,但越來(lái)越接近。項(xiàng)目周圍的活動(dòng)表明,價(jià)值類很快就會(huì)出現(xiàn)在JDK預(yù)覽中。?
在這些有趣的技術(shù)工作之外,讓人感覺(jué)到了Java的持續(xù)活力。人們有意愿也有能力去識(shí)別平臺(tái)在哪些方面可以從根本上進(jìn)行改進(jìn),這是保持Java相關(guān)性的真正承諾的證據(jù)。而Project Loom是對(duì)Java未來(lái)持樂(lè)觀態(tài)度的另一個(gè)項(xiàng)目。?
原文標(biāo)題:??Project Valhalla: A look inside Java’s epic refactor??,作者:Matthew Tyson
?































