重塑云上的 Java 語言
云原生亦如此。雖沒有限定的編程語言,但應用所使用的編程語言已經決定了應用部署運行的行為。
Java 誕生于20年前,擁有大量優(yōu)秀的企業(yè)級框架,踐行 OOP 理念,更多體現(xiàn)的是嚴謹以及在長時間運行條件下的穩(wěn)定性和高性能。反觀如今,在要求快速迭代交付的云場景下,語言的簡單性似乎成了首要的要求,而傳統(tǒng)的 Java 語言顯得有一些過于重量了。
本文由阿里巴巴 JVM 團隊技術專家郁磊(花名:梁希)分享 JVM 團隊是如何面對和處理集團巨大的業(yè)務規(guī)模和復雜的業(yè)務場景的。
ElasticHeap
Java 常因為耗資源而受詬病,其中最顯著一點就是 Heap 對內存的占用,即便沒有請求在處理也沒有對象分配,進程仍然會保留完整的堆內存空間,保障 GC 進行分配內存和操作內存的快速敏捷。
AJDK ZenGC/ElasticHeap 雙十一全面支持核心鏈路上百應用和數(shù)十萬實例。
JDK12 開始支持固定時間的觸發(fā) concurrent mark 并在 remark 中收縮 Java 堆歸還內存的功能,然而并未解決在 stw 中增加暫停時間的問題,因此無法在每次 young GC 時做內存歸還。 ElasticHeap 在并發(fā)異步線程中完成內存處理反復 map/unmap 以及 page fault 的開銷,因此任意一次 young GC 都可以敏捷的及時歸還內存,或重新恢復內存使用。
ElasticHeap 阿里巴巴實戰(zhàn)
ElasticHeap場景1:可預測的流量高峰
ElasticHeap 場景 2 :單機運行多個 Java 實例
多個 Java 實例接受的流量任務較為隨機,峰值不會重疊,在閑時可以有效降低多個實例整體的內存占用,提高部署密度。
雙11驗證核心交易系統(tǒng)使用 ElasticHeap 進行低功耗模式運行,大幅降低 WSS(Working Set Size) 規(guī)模的實例。
靜態(tài)編譯
很多云上的新應用不約而同地選擇了 Go 語言,很大的原因是 Go 應用對運行時沒有依賴,靜態(tài)編譯的程序啟動速度快,也不需要通過 JIT 來預熱。在阿里有大量 Java 代碼的前提下,我們是如何為 Java 注入這方面的能力的呢?
Java 靜態(tài)編譯技術是一種激進的 AOT 技術,通過單獨的編譯階段將 Java 程序編譯為本地代碼,在運行時無需傳統(tǒng) Java 虛擬機和運行時環(huán)境,只需操作系統(tǒng)類庫支持即可。其工作基本原理如下圖所示。靜態(tài)編譯技術實現(xiàn)了 Java 語言與原生 native 程序的“合體”,將原本的 Java 程序編譯成為了一個自舉的具有 Java 行為的原生 native 程序,由此兼有 Java 程序和原生 native 程序的優(yōu)點。
JVM 團隊與 SOFAStack 團隊密切合作,在中間件應用上率先實現(xiàn)靜態(tài)編譯的落地。將一個應用的啟動速度從 60 秒優(yōu)化到 3.8 秒,雙十一期間靜態(tài)編譯的應用運行穩(wěn)定,沒有故障, GC 停頓時間在 100 毫秒,在業(yè)務允許范圍之內,內存占用和 RT 與傳統(tǒng) Java 應用持平。
綜上所述,靜態(tài)編譯的應用在穩(wěn)定性、資源占用、RT 響應等各方面指標與傳統(tǒng) Java 應用基本持平的狀況下,將啟動時間降低了 2000% 。
Wisp2
當你用時下最酷炫的 Vert.X 開發(fā)一個簡單的 Web 服務,準備體驗一下最強的性能, QA 同學拿來一臺 1C 2G 的容器讓你壓一下,你卻發(fā)現(xiàn)你怎么也拼不過別人 Go 應用。研究之后發(fā)現(xiàn),原來協(xié)程模型在這樣的少核心的情況下性能要好很多。是時代變了, Java 落伍了?
AJDK Wisp2 回答了這個問題: Java 同樣可以擁有高性能的協(xié)程。今年是 Wisp2 大規(guī)模上線的第一年, Wisp2 具有如下特點:
在整個Java runtime中支持了協(xié)程調度,線程(比如 Socket.getInputStream().read() )阻塞會變成更輕量的協(xié)程切換。
完全兼容 Thread API ,在開啟 Wisp2 的 JDK 中,Thread.start() 實際創(chuàng)建的是一個協(xié)程(輕量級線程),可以類比 Go 只提供協(xié)程關鍵字 go 而沒有暴露線程接口;我們同樣只提供創(chuàng)建協(xié)程的方式,應用可以透明切換到協(xié)程。
支持 work stealing ,調度策略特別適合 web 場景,在高壓力下調度開銷極小。
在今年雙十一, Wisp 支持了上百應用,十萬級容器,其中 90% 的容器已經升級到 Wisp2 。
我們可以看到峰值附近, Wisp2 機器的 CPU 要低 7%( Wisp1 更低,Wisp2的取向是 RT ,因此 CPU 會高一些)左右,這主要是輕量級調度所節(jié)省的 sys CPU 。 0 點的 CPU 是相等的,這也說明一點: Wisp2 解決的是調度開銷,當 CPU 低,調度沒有壓力時是看不出差距的。
從 RT 角度看, Wisp2 機器的 RT 要低 20% 左右, RT 減少明顯的一個原因是這批機器的 CPU 壓力很大,協(xié)程的調度優(yōu)勢更容易體現(xiàn)出來。這樣的優(yōu)勢可以幫助系統(tǒng)摸高到更高的水位,整體地提高利用率而擔心 RT 過高導致系統(tǒng)雪崩。
FDO
雙十一正零點相對后面幾分鐘會有一個明顯的 CPU 峰值,根據(jù)數(shù)據(jù)分析,主要原因是雙十一零點觸發(fā)了 JIT 編譯。 舉個例子,程序里有邏輯:
if (is1111(LocalDate.now())) {
branch2
} else {
branch3
}
假設預熱時一直在走 branch3 ,那么 JIT 有理由相信后續(xù)基本也都會走 branch3 ,而不會對 branch2編譯。在零點時,我們進入 branch2 ,此時就需要觸發(fā)退優(yōu)化重新編譯方法。我們來看 AJDK 如何通過 profiling 解決這個問題。
退優(yōu)化原理及其危害
JDK 運行代碼的時候,采用分層編譯的方式對 Java 方法進行動態(tài)編譯。在最高等級(峰值性能最好)的編譯中,出于性能的考慮,編譯的時候會根據(jù)收集的信息做一些比較樂觀的假設,一旦這些假設條件不滿足了,就會出現(xiàn)退優(yōu)化的現(xiàn)象。比如某個熱點方法中某段代碼僅會在雙十一中執(zhí)行,那么在預熱過程中這段代碼不會被編譯,雙十一到來時這段代碼一旦被執(zhí)行,就會觸發(fā)整個方法的退優(yōu)化。
發(fā)生退優(yōu)化有兩個方面的負面影響,一是需要運行的方法由高效率的編譯執(zhí)行變成了解釋執(zhí)行,運行速度降低百倍以上;二是流量高峰期退優(yōu)化的方法會很快被重新編譯,編譯線程會消耗 CPU 。因此在雙十一這種流量短時間劇增且與預熱流量不太一樣的場景下,退優(yōu)化的危害會特別明顯。
通過 FDO 減少退優(yōu)化
FDO 是 feedback directed optimization 的縮寫,即參考以往 JVM 運行時的編譯信息,指導本次運行時進行更好的編譯。具體的,我們采用了兩個層面的方法來減少退優(yōu)化。
將每次運行時的退優(yōu)化信息記錄到文件中,下次運行時讀取這個文件,在決定是否做樂觀假設的時候參考文件中的信息做判斷,從而減少退優(yōu)化的概率。
信息顯示出現(xiàn)最多的退優(yōu)化與 if-else 相關,占總數(shù)量的一半以上。我們提供了一個方法根據(jù)以往出現(xiàn) if-else 退優(yōu)化的信息,關閉某個路徑上所有相關的樂觀假設。
雙十一中 FDO 的效果
FDO 今年雙十一上線,目標解決兩個問題:
1、雙十一 0 點流量高峰和退優(yōu)化/編譯高峰疊加造成的 CPU 使用率脈沖過高。
2、預熱效率低,壓測經過前長時間預熱后,增大流量時仍然伴隨著大量的編譯及退優(yōu)化。
針對第一個問題,我們收集了雙十一高峰第一分鐘的退優(yōu)化/C2 編譯次數(shù)以及 CPU 數(shù)據(jù)。
可見開啟 FDO 后高峰期 C2 編譯數(shù)目減少約 45% ,退優(yōu)化數(shù)目減少約 70% 。
CPU 數(shù)據(jù)上,高峰期第一分鐘內開啟 FDO 后 CPU 由約 67.5 降低到 63.1 ,降低約 7.0% 。
第二個目標可以通過壓測第一分鐘的 CPU 數(shù)據(jù)驗證。
開啟 FDO ,壓測第一分鐘 CPU 使用率由 66.19 降低到 60.33% ,降低約 10% 。
Grace
ZProfiler 一直是全集團排查 Java 應用各類問題的利器,而 Grace 作為其平臺化的版本,對其實施了一系列的優(yōu)化,從原來的單機版本到現(xiàn)在的 Master/Worker 架構,同時引入了任務排隊機制,在高壓力情況下對用戶的任務進行排隊從而解決 Worker 不堪重負的問題。在可維護性、拓展性、以及用戶體驗上得到了質的提升,為后續(xù)工具平臺的上云、開源事項打下了夯實的基礎。
目前已經集成了 Heap Dump 功能,在繼承 ZProfiler 功能的基礎上做了一定的優(yōu)化,提升了解析引擎的版本,支持更全面的 OQL 語法等等。
JDK11
JDK8 作為一個經典版本,正被大規(guī)模使用,雖然從 JDK6 和 7 遷移上來有一定的陣痛,但是升級后普遍的反饋是:“真香”。
OpenJDK 8的下一個穩(wěn)定版本是 OpenJDK 11 。JVM 團隊自然會在這個方向上積極跟進,目前 AJDK11 支持了 AJDK8 的 Wisp2 、多租戶特性。本次雙十一的部分集群已經上線到 JDK11 ,表現(xiàn)穩(wěn)定。
升級 JDK11 是否會和升級 JDK8 一樣給我們帶來同樣的的驚喜呢?在 JDK11 上我們可以體驗到最新的 ZGC 。
ZGC
JDK11 引入了一個重要特性: ZGC 內存垃圾回收器。這個垃圾回收器號稱能夠在幾十 GB 至若干 TB 的堆上把暫停時間保持在 10ms 以內。許多 Java 開發(fā)者苦于過去的垃圾回收器的暫停時間帶來延遲, ZGC 短暫停的特性未來無疑會成為 Java 開發(fā)者的新寵。
目前 ZGC 在 OpenJDK 中仍然處于實驗特性,而且 JDK11 尚未在產業(yè)界完全普及, JDK11 只支持 Linux 上的 ZGC( MacOS 和 Windows 的 ZGC 預計在 2020 年 3 月發(fā)布的 JDK14 版本才會支持),許多 Java 開發(fā)者仍然只能垂涎欲滴,處于觀望狀態(tài)。
向來敢于吃螃蟹的我們豈能望而卻步?阿里 JVM 團隊和數(shù)據(jù)庫團隊已經開始讓數(shù)據(jù)庫應用運行在 ZGC 上,并根據(jù)運行的效果對 ZGC 進行了相應的改進工作,包括 ZGC 的頁緩存機制優(yōu)化、ZGC的觸發(fā)時機優(yōu)化等等。
從 9 月開始,兩個團隊推動線上數(shù)據(jù)庫應用在 ZGC 上運行,目前已經穩(wěn)定運行兩個月,并順利通過雙十一大考。線上反饋的效果可喜可賀:
1、 JVM 暫停時間保持在官方的 10ms 以內;
2、 ZGC 大大改善了線上運行集群的平均 RT 與毛刺指標。
小結
從上述的功能特性可以看到 AJDK 已經從一個傳統(tǒng)的 Managed Runtime 脫胎換骨。今后 AJDK 將繼續(xù)致力于提高云上的應用的開發(fā)體驗,通過底層的創(chuàng)新為上層應用提供更多的可能。
在 Dragonwell 上使用 AJDK 功能
上述的這些經過雙十一考驗的功能都將隨著 Dragonwell 陸續(xù)開源和交付到廣大用戶手中,敬請關注。
Alibaba Dragonwell 8 是一款免費的 OpenJDK 發(fā)行版。它提供長期支持,包括性能增強和安全修復。Alibaba Dragonwell 8 目前支持 X86-64/Linux 平臺,在數(shù)據(jù)中心大規(guī)模 Java 應用部署情況下, 可以大幅度提高穩(wěn)定性、效率以及性能。Alibaba Dragonwell 8 是 OpenJDK 的下游( friendly fork ),使用了和 OpenJDK 一樣的 license。Alibaba Dragonwell 8 與 Java SE 標準兼容,用戶可以使用 Alibaba Dragonwell 8 開發(fā)和運行 Java 應用程序。此次開源的 Alibaba Dragonwell 8 是阿里巴巴內部 OpenJDK 定制版 AJDK 的開源版本, AJDK 為在線電商,金融,物流做了結合業(yè)務場景的優(yōu)化,運行在超大規(guī)模的,1,000,000+
服務器的阿里巴巴數(shù)據(jù)中心。
近期我們正在緊密籌備 Alibaba Dragonwell 11 的 release 。 Dragonwell 11 是基于 OpenJDK 11 的 Dragonwell 發(fā)行版本,擁有更多特性,可以更多地為云上場景賦能,模塊化更加清晰,并將獲得長期的支持,因此推薦大家關注和適時升級。