【openJDK系列2】雲原生時代,Java危矣?

本文轉載自:https://mp.weixin.qq.com/s/fV...java

Java 誕生距今已有 25 年,但它仍然長期佔據着「天下第一」編程語言的寶座。只是其統治地位並不是堅如盤石,反倒能夠說是危機四伏。雲原生時代,Java 技術體系的許多前提假設都受到了挑戰,目前已經有可預見的、足以威脅動搖其根基的潛在可能性正在醞釀。同時,像 Golang、Rust 這樣的新生語言,以及 C、C++、C#、Python 等老對手也都對 Java 的市場份額虎視眈眈。面對危機,Java 正在嘗試哪些變革?將來,Java 是會繼續向前、再攀高峯,仍是由盛轉衰?在今天由極客邦科技舉辦的 QCon 全球軟件開發大會 2020(深圳站)上,遠光軟件研究院院長、《深刻理解 Java 虛擬機》系列書籍做者周志明發表了主題演講《雲原生時代的 Java》,如下內容爲演講整理。  linux

今天,25歲的Java仍然是最具備統治力的編程語言,長期佔據編程語言排行榜的首位,擁有一千二百萬的龐大開發者羣體,全世界有四百五十億部物理設備使用着Java技術,同時,在雲端數據中心的虛擬化環境裏,還運行着超過兩百五十億個Java虛擬機的進程實例 (數據來自Oracle的WebCast)。程序員

以上這些數據是Java過去25年巨大成就的功勳佐證,更是Java技術體系維持本身「天下第一」編程語言的堅實壁壘。Java與其餘語言競爭,底氣歷來不在於語法、類庫有多麼先進好用,而是來自它龐大的用戶羣和極其成熟的軟件生態,這在朝夕之間難以撼動。然而,這個如今看起來仍然堅如盤石的Java帝國,其統治地位的穩固程度不只沒有高枕無憂,反而說是危機四伏也不爲過。目前已經有了可預見的、足以威脅動搖其根基的潛在可能性正在醞釀,並隨雲原生時代而降臨。docker

Java 的危機數據庫

Java與雲原生的矛盾,來源於Java誕生之初,植入到它基因之中的一些基本的前提假設已經逐漸開始被動搖,甚至已經再也不成立。編程

我舉個例子,每一位Java的使用者都據說過「一次編寫,處處運行」(Write Once, Run Anywhere)這句口號。20多年前,Java成熟以前,開發者若是但願程序在Linux、Solaris、Windows等不一樣平臺,在x8六、AMD6四、SPARC、MIPS、ARM等不一樣指令集架構上都能正常運行,就必須針對每種組合,編譯出對應的二進制發行包,或者索性直接分發源代碼,由使用者在本身的平臺上編譯。瀏覽器

面對這個問題,Java經過語言層虛擬化的方式,令每個Java應用都自動取得平臺無關(Platform Independent)、架構中立(Architecture Neutral)的先天優點,讓同一套程序格式得以在不一樣指令集架構、不一樣操做系統環境下都能運行且獲得一致的結果,不只方便了程序的分發,還避免了各類平臺下內存模型、線程模型、字節序等底層細節差別對程序編寫的干擾。在當年,Java的這種設計帶有使人趨之若鶩的強大吸引力,直接開啓了託管語言(Managed Language,如Java、.NET)的一段興盛期。服務器

面對相同的問題,今天的雲原生選擇以操做系統層虛擬化的方式,經過容器實現的不可變基礎設施去解決。不可變基礎設施這個概念出現得比雲原生要早,本來是指該如何避免因爲運維人員對服務器運行環境所作的持續的變動而致使的意想不到的反作用。但在雲原生時代,它的內涵已再也不侷限於方便運維、程序升級和部署的手段,而是昇華一種爲嚮應用代碼隱藏環境複雜性的手段,是分佈式服務得以成爲一種可廣泛推廣的普適架構風格的必要前提。數據結構

將程序連同它的運行環境一塊兒封裝到穩定的鏡像裏,現已經是一種主流的應用程序分發方式。Docker一樣提出過「一次構建,處處運行」(Build Once, Run Anywhere)的口號,儘管它只能提供環境兼容性和有侷限的平臺無關性(指系統內核功能以上的ABI兼容),且徹底不可能支撐架構中立性,因此將「一次構建,處處運行」與「一次編寫,處處運行」對立起來並不嚴謹恰當,可是無能否認,今天Java技術「一次編譯,處處運行」的優點,已經被容器大幅度地削弱,再也不是大多數服務端開發者技術選型的主要考慮因素了。多線程

若是僅僅是優點的削弱,並不足以成爲Java的直接威脅,充其量只是一個潛在的不利因素,但更加迫在眉睫的風險來自於那些與技術潮流直接衝突的假設。譬如,Java整體上是面向大規模、長時間的服務端應用而設計的,嚴(luō)謹(suō)的語法利於約束全部人寫出較一致的代碼;靜態類型動態連接的語言結構,利於多人協做開發,讓軟件觸及更大規模;即時編譯器、性能制導優化、垃圾收集子系統等Java最具表明性的技術特徵,都是爲了便於長時間運行的程序能享受到硬件規模發展的紅利。

另外一方面,在微服務的背景下,提倡服務圍繞業務能力而非技術來構建應用,再也不追求實現上的一致,一個系統由不一樣語言,不一樣技術框架所實現的服務來組成是徹底合理的;服務化拆分後,極可能單個微服務再也不須要再面對數10、數百GB乃至TB的內存;有了高可用的服務集羣,也無須追求單個服務要7×24小時不可間斷地運行,它們隨時能夠中斷和更新。

同時,微服務又對應用的容器化親和性,譬如鏡像體積、內存消耗、啓動速度,以及達到最高性能的時間等方面提出了新的要求。這兩年的網紅概念Serverless也進一步增長這些因素的考慮權重,而這些卻正好都是Java的弱項:哪怕再小的Java程序也要帶着完整的虛擬機和標準類庫,使得鏡像拉取和容器建立效率下降,進而使整個容器生命週期拉長。基於Java虛擬機的執行機制,使得任何Java的程序都會有固定的基礎內存開銷,以及固定的啓動時間,並且Java生態中普遍採用的依賴注入進一步將啓動時間拉長,使得容器的冷啓動時間很難縮短。 

軟件工業中已經出現過不止一塊兒因Java這些弱點而致使失敗的案例,如JRuby編寫的Logstash,本來是同時承擔部署在節點上的收集端(Shipper)和專門轉換處理的服務端(Master)的職責,後來由於資源佔用的緣由,被Elstaic.co用Golang的Filebeat代替了Shipper部分的職能;又如Scala語言編寫的邊車代理Linkerd,做爲服務網格概念的提出者,卻最終被Envoy所取代,其主要弱點之一也是因爲Java虛擬機的資源消耗所帶來的劣勢。 

雖然在雲原生時代依然有不少適合Java發揮的領域,可是具有彈性與韌性、隨時能夠中斷重啓的微型服務的確已經造成了一股潮流,在逐步蠶食大型系統的領地。正是因爲潮流趨勢的改變,新一代的語言與技術尤爲重視輕量化和快速響應能力,大多又從新迴歸到了原生語言(Native Language,如Golang、Rust)之上。

Java 的變革

面對挑戰,Java的開發者和社區都沒有退縮,它們在各自的領域給出了不少優秀的解決方案,涌現瞭如Quarkus、Micronaut、Helidon等一大批以提高Java在雲原生環境下的適應性爲賣點的框架。

不過,今天咱們的主題將聚焦在由Java官方自己所推動的項目上。在圍繞Java 25週年的研討和佈道活動中,官方的設定是以「面向將來的變革」(Innovating for the Future)爲基調,你有可能在此以前已經據說過其中某個(某些)項目的名字和改進點,但這裏咱們不只關心這些項目改進的是什麼,還更關心它們背後的動機與困難、帶來的收益,以及要付出的代價。

    Innovating for the Future

Project Leyden

對於原生語言的挑戰,最有力最完全的反擊手段無疑是將字節碼直接編譯成能夠脫離Java虛擬機的原生代碼。若是真的可以生成脫離Java虛擬機運行的原生程序,將意味着啓動時間長的問題可以完全解決,由於此時已經不存在初始化虛擬機和類加載的過程;也意味着程序立刻就能達到最佳的性能,由於此時已經不存在即時編譯器運行時編譯,全部代碼都是在編譯期編譯和優化好的(以下圖所示);沒有了Java虛擬機、即時編譯器這些額外的部件,也就意味着可以省去它們本來消耗的那部份內存資源與鏡像體積。

     Java Performance Matrices(圖片來源)

但同時,這也是風險係數最高、實現難度最大的方案。

Java並不是沒有嘗試走過這條路,從Java 2以前的GCJ(GNU Compiler for Java),到後來的Excelsior JET,再到2018年Oracle Labs啓動的GraalVM中的SubstrateVM模塊,最後到2020年中期剛創建的Leyden項目,都在朝着提早編譯(Ahead-of-Time Compilation,AOT)生成原生程序這個目標邁進。

Java支持提早編譯最大的困難在於它是一門動態連接的語言,它假設程序的代碼空間是開放的(Open World),容許在程序的任什麼時候候經過類加載器去加載新的類,做爲程序的一部分運行。要進行提早編譯,就必須放棄這部分動態性,假設程序的代碼空間是封閉的(Closed World),全部要運行的代碼都必須在編譯期所有可知。這一點不只僅影響到了類加載器的正常運做,除了沒法再動態加載外,反射(經過反射能夠調用在編譯期不可知的方法)、動態代理、字節碼生成庫(如CGLib)等一切會運行時產生新代碼的功能都再也不可用,若是將這些基礎能力直接抽離掉,Helloworld仍是能跑起來,但Spring確定跑不起來,Hibernate也跑不起來,大部分的生產力工具都跑不起來,整個Java生態中絕大多數上層建築都會轟然崩塌。 

要得到有實用價值的提早編譯能力,只有依靠提早編譯器、組件類庫和開發者三方一塊兒協同纔可能辦到。因爲Leyden剛剛開始,幾乎沒有公開的資料,因此下面我是以SubstrateVM爲目標對象進行的介紹: 

  • 有一些功能,像反射這樣的基礎特性是不可能妥協的,折衷的解決辦法是由用戶在編譯期,以配置文件或者編譯器參數的形式,明確告知編譯器程序代碼中有哪些方法是隻經過反射來訪問的,編譯器將方法的添加到靜態編譯的範疇之中。同理,全部使用到動態代理的地方,也必須在事先列明,在編譯期就將動態代理的字節碼所有生成出來。其餘全部沒法經過程序指針分析(Points-To Analysis)獲得的信息,譬如程序中用到的資源、配置文件等等,也必須照此處理。
  • 另外一些功能,如動態生成字節碼也十分經常使用,但用戶本身每每沒法得知那些動態字節碼的具體信息,就只能由用到CGLib、javassist等庫的程序去妥協放棄。在Java世界中也許最典型的場景就是Spring用CGLib來進行類加強,默認狀況下,每個Spring管理的Bean都要用到CGLib。從Spring Framework 5.2開始增長了@proxyBeanMethods註解來排除對CGLib的依賴,僅使用標準的動態代理去加強類。

2019年起,Pivotal的Spring團隊與Oracle Labs的GraalVM團隊共同孵化了Spring GraalVM Native項目,這個目前仍處於Experimental / Alpha狀態的項目,可以讓程序先以傳統方式運行(啓動)一次,自動化地找出程序中的反射、動態代理的代碼,代替用戶向編譯器提供絕大部分所需的信息,並能將容許啓動時初始化的Bean在編譯期就完成初始化,直接繞過Spring程序啓動最慢的階段。這樣從啓動到程序能夠提供服務,耗時竟可以低於0.1秒。

    Spring Boot Startup Time(數據來源)

以原生方式運行後,縮短啓動時間的效果立竿見影,通常會有數十倍甚至更高的改善,程序容量和內存消耗也有必定程度的降低。不過至少目前而言,程序的運行效率仍是要弱於傳統基於Java虛擬機的方式,雖然即時編譯器有編譯時間的壓力,但因爲能夠進行基於假設的激進優化和運行時性能度量的制導優化,使得即時編譯器的效果仍要優於提早編譯器,這方面須要GraalVM編譯器團隊的進一步努力,也須要從語言改進上入手,讓Java變得更適合被編譯器優化。

Project Valhalla

Java語言上可感知的語法變化,多數來自於Amber項目,它的項目目標是持續優化語言生產力,近期(JDK 1五、16)會有不少來自這個項目的特性,如Records、Sealed Class、Pattern Matching、Raw String Literals等實裝到生產環境。 

然而語法不只與編碼效率相關,與運行效率也有很大關係。「程序=代碼+數據」這個提法至少在衡量運行效率上是合適的,不管是託管語言仍是原生語言,最終產物都是處理器執行的指令流和內存存儲的數據結構。Java、.NET、C、C++、Golang、Rust等各類語言誰更快,取決於特定場景下,編譯器生成指令流的優化效果,以及數據在內存中的結構佈局。

Java即時編譯器的優化效果拔羣,可是因爲Java「一切皆爲對象」的前提假設,致使在處理一系列不一樣類型的小對象時,內存訪問性能很是拉垮,這點是Java在遊戲、圖形處理等領域一直難有建樹的重要制約因素,也是Java創建Valhalla項目的目標初衷。 

這裏舉個例子來講明此問題,若是我想描述空間裏面若干條線段的集合,在Java中定義的代碼會是這樣的:

public record Point(float x, float y, float z) {}
public record Line(Point start, Point end) {}
Line[] lines;

面向對象的內存佈局中,對象標識符(Object Identity)存在的目的是爲了容許在不暴露對象結構的前提下,依然能夠引用其屬性與行爲,這是面向對象編程中多態性的基礎。在Java中堆內存分配和回收、空值判斷、引用比較、同步鎖等一系列功能都會涉及到對象標識符,內存訪問也是依靠對象標識符來進行鏈式處理的,譬如上面代碼中的「若干條線段的集合」,在堆內存中將構成以下圖的引用關係: 

Object Identity / Memory Layout

計算機硬件通過25年的發展,內存與處理器雖然都在進步,可是內存延遲與處理器執行性能之間的馮諾依曼瓶頸(Von Neumann Bottleneck)不只沒有縮減,反而還在持續加大,「RAM Is the New Disk」已經從嘲諷梗逐漸成爲了現實。

一次內存訪問(將主內存數據調入處理器Cache)大約須要耗費數百個時鐘週期,而大部分簡單指令的執行只須要一個時鐘週期而已。所以,在程序執行性能這個問題上,若是編譯器能減小一次內存訪問,可能比優化掉幾10、幾百條其餘指令都來得更有效果。 

額外知識:馮諾依曼瓶頸

不一樣處理器(現代處理器都集成了內存管理器,之前是在北橋芯片中)的內存延遲大概是40-80納秒(ns,十億分之一秒),而根據不一樣的時鐘頻率,一個時鐘週期大概在0.2-0.4納秒之間,如此短暫的時間內,即便真空中傳播的光,也僅僅可以行進10釐米左右。

數據存儲與處理器執行的速度矛盾是馮諾依曼架構的主要侷限性之一,1977年的圖靈獎得主John Backus提出了「馮諾依曼瓶頸」這個概念,專門用來描述這種侷限性。 

編譯器的確在努力減小內存訪問,從JDK 6起,HotSpot的即時編譯器就嘗試經過逃逸分析來作標量替換(Scalar Replacement)和棧上分配(Stack Allocations)優化,基本原理是若是能經過分析,得知一個對象不會傳遞到方法以外,那就不須要真實地在對中建立完整的對象佈局,徹底能夠繞過對象標識符,將它拆散爲基本的原生數據類型來建立,甚至是直接在棧內存中分配空間(HotSpot並無這樣作),方法執行完畢後隨着棧幀一塊兒銷燬掉。

不過,逃逸分析是一種過程間優化(Interprocedural Optimization),很是耗時,也很難處理那些理論有可能但實際不存在的狀況。相同的問題在C、C++中卻並不存在,上面場景中,程序員只要將Point和Line都定義爲struct便可,C#中也有struct,是依靠.NET的值類型(Value Type)來實現的。Valhalla項目的核心改進就是提供相似的值類型支持,提供一個新的關鍵字(inline),讓用戶能夠在不須要向方法外部暴露對象、不須要多態性支持、不須要將對象用做同步鎖的場合中,將類標識爲值類型,此時編譯器就可以繞過對象標識符,以平坦的、緊湊的方式去爲對象分配內存。 

有了值類型的支持後,如今Java泛型中使人詬病的不支持原數據類型(Primitive Type)、頻繁裝箱問題也就隨之迎刃而解,如今Java的包裝類,理所固然地會以表明原生類型的值類型來從新定義,這樣Java泛型的性能會獲得明顯的提高,由於此時Integer與int的訪問,在機器層面看徹底能夠達到一致的效率。

Project Loom

Java語言抽象出來隱藏了各類操做系統線程差別性的統一線程接口,這曾經是它區別於其餘編程語言(C/C++表示有被冒犯到)的一大優點,不過,統一的線程模型不見得永遠都是正確的。

Java目前主流的線程模型是直接映射到操做系統內核上的1:1模型,這對於計算密集型任務這很合適,既不用本身去作調度,也利於一條線程跑滿整個處理器核心。但對於I/O密集型任務,譬如訪問磁盤、訪問數據庫佔主要時間的任務,這種模型就顯得成本高昂,主要在於內存消耗和上下文切換上:64位Linux上HotSpot的線程棧容量默認是1MB,線程的內核元數據(Kernel Metadata)還要額外消耗2-16KB內存,因此單個虛擬機的最大線程數量通常只會設置到200至400條,當程序員把數以百萬計的請求往線程池裏面灌時,系統即使能處理得過來,其中的切換損耗也至關可觀。 

Loom項目的目標是讓Java支持額外的N:M線程模型,請注意是「額外支持」,而不是像當年從綠色線程過渡到內核線程那樣的直接替換,也不是像Solaris平臺的HotSpot虛擬機那樣經過參數讓用戶二選其一。

Loom項目新增長一種「虛擬線程」(Virtual Thread,之前以Fiber爲名進行宣傳過,但由於要頻繁解釋啥是Fiber因此如今放棄了),本質上它是一種有棧協程(Stackful Coroutine),多條虛擬線程能夠映射到同一條物理線程之中,在用戶空間中自行調度,每條虛擬線程的棧容量也可由用戶自行決定。 

     Virtual Thread

同時,Loom項目的另外一個目標是要盡最大可能保持原有統一線程模型的交互方式,通俗地說就是原有的Thread、J.U.C、NIO、Executor、Future、ForkJoinPool等這些多線程工具都應該能以一樣的方式支持新的虛擬線程,原來多線程中你理解的概念、編碼習慣大多數都可以繼續沿用。

爲此,虛擬線程將會與物理線程同樣使用java.lang.Thread來進行抽象,只是在建立線程時用到的參數或者方法稍有不一樣(譬如給Thread增長一個Thread.VIRTUAL_THREAD參數,或者增長一個startVirtualThread()方法)。這樣現有的多線程代碼遷移到虛擬線程中的成本就會變得很低,而代價就是Loom的團隊必須作更多的工做以保證虛擬線程在大部分涉及到多線程的標準API中都可以兼容,甚至在調試器上虛擬線程與物理線程看起來都會有一致的外觀。但很難所有都支持,譬如調用JNI的本地棧幀就很難放到虛擬線程上,因此一旦遇到本地方法,虛擬線程就會被綁定(Pinned)到一條物理線程上。 

Loom的另外一個重點改進是支持結構化併發(Structured Concurrency),這是2016年才提出的新的併發編程概念,但很快就被諸多編程語言所吸納。它是指程序的併發行爲會與代碼的結構對齊,譬如如下代碼所示,按照傳統的編程觀念,若是沒有額外的處理(譬如無中生有地弄一個await關鍵字),那在task1和task2提交以後,程序應該繼續向下執行:

ThreadFactory factory = Thread.builder().virtual().factory();
try (var executor = Executors.newThreadExecutor(factory)) {
 executor.submit(task1);
 executor.submit(task2);
} // blocks and waits

可是在結構化併發的支持下,只有兩個並行啓動的任務線程都結束以後,程序纔會繼續向下執行,很好地以同步的編碼風格,來解決異步的執行問題。事實上,「Code like sync,Work like async」正是Loom簡化併發編程的核心理念。

Project Portola

Portola項目的目標是將OpenJDK向Alpine Linux移植。Alpine Linux是許多Docker容器首選的基礎鏡像,由於它只有5 MB大小,比起其餘Cent OS、Debain等動輒一百多MB的發行版來講,更適合用於容器環境。不過Alpine Linux爲了儘可能瘦身,默認是用musl做爲C標準庫的,而非傳統的glibc(GNU C library),所以要以Alpine Linux爲基礎製做OpenJDK鏡像,必須先安裝glibc,此時基礎鏡像大約有12 MB。Portola計劃將OpenJDK的上游代碼移植到musl,並經過兼容性測試。使用Portola製做的標準Java SE 13鏡像僅有41 MB,不只遠低於Cent OS的OpenJDK(大約396 MB),也要比官方的slim版(約200 MB)要小得多。 

$ sudo docker build .
Sending build context to Docker daemon 2.56kB
Step 1/8 : FROM alpine:latest as build
latest: Pulling from library/alpine
bdf0201b3a05: Pull complete
Digest: sha256:28ef97b8686a0b5399129e9b763d5b7e5ff03576aa5580d6f4182a49c5fe1913
Status: Downloaded newer image for alpine:latest
 ---> cdf98d1859c1
Step 2/8 : ADD https://download.java.net/java/early_access/alpine/16/binaries/openjdk-13-ea+16_linux-x64-musl_bin.tar.gz /opt/jdk/
Downloading [==================================================>] 195.2MB/195.2MB
 ---> Using cache
 ---> b1a444e9dde9
Step 3/7 : RUN tar -xzvf /opt/jdk/openjdk-13-ea+16_linux-x64-musl_bin.tar.gz -C /opt/jdk/
 ---> Using cache
 ---> ce2721c75ea0
Step 4/7 : RUN ["/opt/jdk/jdk-13/bin/jlink", "--compress=2", "--module-path", "/opt/jdk/jdk-13/jmods/", "--add-modules", "java.base", "--output", "/jlinked"]
 ---> Using cache
 ---> d7b2793ed509
Step 5/7 : FROM alpine:latest
 ---> cdf98d1859c1
Step 6/7 : COPY --from=build /jlinked /opt/jdk/
 ---> Using cache
 ---> 993fb106f2c2
Step 7/7 : CMD ["/opt/jdk/bin/java", "--version"] - to check JDK version
 ---> Running in 8e1658f5f84d
Removing intermediate container 8e1658f5f84d
 ---> 350dd3a72a7d
Successfully built 350dd3a72a7d
$ sudo docker tag 350dd3a72a7d jdk-13-musl/jdk-version:v1
$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
jdk-13-musl/jdk-version v1 350dd3a72a7d About a minute ago 41.7MB
alpine latest cdf98d1859c1 2 weeks ago 5.53M

Java 的將來

雲原生時代,Java技術體系的許多前提假設都受到了挑戰,「一次編譯,處處運行」、「面向長時間大規模程序而設計」、「從開放的代碼空間中動態加載」、「一切皆爲對象」、「統一線程模型」,等等。技術發展迭代不會停歇,沒有必要堅持什麼「永恆的真理」,舊的原則被打破,只要合理,即是創新。

Java語言意識到了挑戰,也意識到了要面向將來而變革。文中提到的這些項目,Amber和Portola已經明確會在2021年3月的Java 16中發佈,至少也會達到Feature Preview的程度:

  • JEP 394:Pattern Matching for instanceof
  • JEP 395:Records
  • JEP 397:Sealed Classes
  • JEP 386:Alpine Linux Port

至於更受關注,同時也是難度更高的 Valhalla 和 Loom 項目,目前仍然沒有明確的版本計劃信息,儘管它們已經開發了數年時間,很是但願可以趕在 Java 17 這個 LTS 版本中面世,但前路仍是困難重重。

至於難度最高、建立時間最晚的 Leyden 項目,目前還徹底處於特性討論階段,連個胚胎都算不上。對於 Java 的原生編譯,咱們中短時間內只可能寄但願於 Oracle 的 GraalVM。

將來一段時間,是Java重要的轉型窗口期,若是做爲下一個LTS版的Java 17,可以成功集Amber、Portola、Valhalla、Loom和Panama(用於外部函數接口訪問,本文沒有提到)的新能力、新特性於一身,GraalVM也能給予足夠強力支持的話,那Java 17 LTS大機率會是一個里程碑式的版本,帶領着整個Java生態從大規模服務端應用,向新的雲原生時代軟件系統轉型。可能成爲比肩當年從面向嵌入式設備與瀏覽器Web Applets的Java 1,到確立現代Java語言方向(Java SE/EE/ME和JavaCard)雛形的Java 2轉型那樣的里程碑。 

可是,若是Java不能加速本身的發展步伐,那由強大生態所構建的護城河終究會消耗殆盡,被Golang、Rust這樣的新生語言,以及C、C++、C#、Python等老對手蠶食掉很大一部分市場份額,以致被迫從「天下第一」編程語言的寶座中退位。 

Java的將來是繼續向前,再攀高峯,仍是由盛轉衰,鋒芒挫縮,你我拭目以待。

相關文章
相關標籤/搜索