本書作者是阿里靜態(tài)編譯研究團隊的核心成員,曾任華為高級工程師,一直從事靜態(tài)編譯技術的研究與落地。本書得到業(yè)界多位知名專家的鼎力推薦,是深度、系統研究Java靜態(tài)編譯技術的鮮見資料。
閱讀本書,讀者可以了解Java靜態(tài)編譯技術的特點、實現機制、優(yōu)缺點等,以將其應用到自己的業(yè)務實踐中。
【本書分為三部分】:
第1部分(第1~4章)首先概要介紹Java編譯器的演進過程,然后介紹Java靜態(tài)編譯的基本原理、總體能力和發(fā)展前景,主要向讀者說明Java靜態(tài)編譯技術是什么、業(yè)界主流實現方案、GraalVM結構及靜態(tài)編譯應用的流程。
第2部分(第5~12章)詳細介紹GraalVM如何實現Java的靜態(tài)編譯,著重介紹如何支持現有Java特性,涵蓋靜態(tài)編譯的總體流程、重要機制(擴展、替換、類提前初始化優(yōu)化機制)、動態(tài)特性(反射和序列化)實現、跨語言編程能力等。讀者可以掌握GraalVM對Java靜態(tài)編譯技術的具體實現原理,了解動態(tài)的Java特性是如何被靜態(tài)化支持的,洞悉靜態(tài)編譯本質。
第3部分(第13~15章)主要關注GraalVM的實踐應用,介紹如何將傳統Java應用編譯為二進制可執(zhí)行文件或靜態(tài)庫文件并部署上云,以及與傳統的Java程序完全不同的編譯調試技術。讀者可以獲悉靜態(tài)編譯實踐中常見問題的解決方案,以更快擁有靜態(tài)編譯技術的應用實踐能力。
1)多名專家聯袂推薦:北京大學計算機科學技術系主任胡振江教授、阿里蔡景現(多。、華為方舟編譯器總架構師葉寒棟、GraalVM核心開發(fā)人員鄭雨迪聯袂推薦。
2)阿里資深專家撰寫:作者林子熠阿里靜態(tài)編譯研究團隊的核心成員,曾任華為高級工程師,一直從事靜態(tài)編譯技術的研究與落地。
3)Java應用性能提升制勝法寶:通過靜態(tài)編譯技術實現Java應用冷啟動,實現性能質的飛躍。
4)全方位落地指導:深入原理,給出具體應用與調試技術,指導讀者做好平穩(wěn)落地工作。
【為什么寫作本書】
Java語言可謂程序語言界的常青藤,自1996年誕生以來,長期在受歡迎的編程語言排行榜中占據領先地位。除了語言本身的優(yōu)秀特性之外,Java語言持續(xù)演進、不斷發(fā)展也是它能夠保持長盛不衰的重要原因。
近年來,隨著云原生浪潮的興起,越來越多的應用被部署在了云廠商的云服務環(huán)境中,以計算資源的形式為用戶提供服務。在這種趨勢下,應用本身越來越小,對跨平臺的需求越來越弱(因為平臺問題已經由云廠商解決了),但是對應用快速啟動、即起即用和高性能執(zhí)行的需求越來越強。Java程序的冷啟動問題在這種場景下就顯得格外突出,成為開發(fā)人員在選擇編程語言時的主要減分項。根據著名的TIOBE編程語言流行趨勢索引統計,Java語言的市場占有率從2016年1月的21.4%跌至2021年8月的10%,在C和Python之后,排名第三。
難道使用Java語言就只能忍受冷啟動問題嗎?Java社區(qū)和工業(yè)界一直在探索冷啟動問題的解決之道,希望使用Java的用戶在享受Java豐富生態(tài)的同時,還能獲得良好的啟動性能。比如OpenJDK提出的AppCDS(Application Class Data Sharing)技術,可以將已經加載的類的元數據導出到文件,在下次啟動時直接從文件導入這些數據,無須再次經過類的解析和加載等過程,由此削減啟動時的類加載開銷。但是,因為Java的冷啟動問題的根源在于JVM本身,所以在JVM之上做的各種優(yōu)化的效果都是有限的,難以實現質的飛躍。
從根本上審視Java冷啟動問題可以發(fā)現,啟動一個Java程序并讓它達到性能的峰值需要經過VM初始化應用程序初始化字節(jié)碼解釋執(zhí)行JIT編譯熱點函數執(zhí)行JIT編譯后的本地代碼(native code)等環(huán)節(jié),且不論在這些環(huán)節(jié)上能夠做出何種優(yōu)化,單這么長的一條鏈路已足以說明冷啟動問題之復雜、難解。如果不能打破這條鏈路,而只是在各個環(huán)節(jié)上進行優(yōu)化,恐怕很難達到理想的效果。那么是否能夠打破這條長鏈,越過中間環(huán)節(jié)直達后一步,像C語言一樣直接將Java代碼編譯為本地代碼執(zhí)行呢?
答案是肯定的,這就是本書要為讀者展現的Java靜態(tài)編譯技術。Oracle公司推出的開源高性能多語言運行平臺項目GraalVM,打造了一個包括靜態(tài)編譯器和輕量級運行時的Java靜態(tài)編譯框架,可以將Java程序從字節(jié)碼直接編譯為本地可執(zhí)行應用程序。與在JVM下執(zhí)行相比,靜態(tài)編譯后的Java程序的啟動速度能夠提升兩個數量級,完全解決了冷啟動問題,實現了Java應用程序啟動性能的質的突破。目前關于GraalVM靜態(tài)編譯的大多數資料都是開發(fā)團隊發(fā)布的技術文檔、博客和GitHub上的開發(fā)相關問題討論,而缺少系統全面性的資料介紹,尤其缺乏中文資料。因此國內的廣大程序開發(fā)者和技術愛好者對其并不了解。本書旨在填補這方面的空白,使讀者能夠系統性了解并掌握GraalVM靜態(tài)編譯技術。
【本書特色】
本書將為讀者詳細解釋GraalVM中的Java靜態(tài)編譯技術,不僅帶你了解GraalVM的靜態(tài)編譯框架的使用方法,更重要的是向你介紹其背后的實現原理。有興趣的讀者在閱讀完本書后可以獨立閱讀甚至修改GraalVM中的源碼,并向社區(qū)提出自己的功能改進建議或Bug修復的補丁,幫助GraalVM更好地發(fā)展。本書側重介紹GraalVM靜態(tài)編譯框架和運行時的應用與原理,而不太涉及編譯部分。
原因如下:其一,GraalVM的靜態(tài)編譯中使用的編譯器并不專用于Java靜態(tài)編譯,如可用于代替HotSpot的C2編譯器,其內容博大精深,足以單獨成書,所以不會過多闡述;其二,Java靜態(tài)編譯的難點并不在于編譯本身,而是在于確定編譯的范圍以及對JVM原本動態(tài)運行時的改造適配等,因為JVM的實時編譯器早已實現對Java字節(jié)碼的編譯。
【如何閱讀本書】
本書分為三部分,分別從應用、實現原理和具體實例三個方面進行闡述。
部分(第1~4章)從整體上介紹GraalVM項目及其靜態(tài)編譯子項目Substrate VM。
第1章向讀者介紹Java靜態(tài)編譯產生的技術原因Java冷啟動問題的產生和由來。
第2章首先對GraalVM做概要介紹,然后分別介紹Substrate VM和方舟編譯器這兩種實現方案,并對比它們的技術特點。
第3章向讀者介紹Oracle GraalVM項目的整體結構。
第4章介紹使用GraalVM靜態(tài)編譯Java應用的詳細步驟。
第二部分(第5~12章)主要介紹GraalVM中靜態(tài)編譯框架子項目Substrate VM的實現原理。
第5章介紹Substrate VM靜態(tài)編譯框架的實現與總體流程。
第6章介紹Substrate VM中的功能擴展機制Feature機制,框架中的各個具體功能點都是通過該機制實現的。
第7章介紹編譯時的程序元素替換功能Substitution機制,該機制實現了無侵入性的程序元素替換能力,在靜態(tài)編譯框架的運行時實現中有基礎性的地位。
第8章介紹Substrate VM的類提前初始化優(yōu)化技術,該技術將符合條件的類在編譯時初始化,不但節(jié)省了運行時初始化的開銷,而且無須分析已經運行過的類初始化函數,因此降低了編譯時的靜態(tài)分析開銷。
第9章和第10章分別介紹兩種具有代表性的Java動態(tài)特性反射和序列化的靜態(tài)化實現過程。
第11章和第12章介紹Substrate VM的跨語言編程能力。
第三部分(第13~15章)通過兩個實例介紹Java靜態(tài)編譯技術的實踐,并在后介紹程序在靜態(tài)編譯后的產物native image的調試方法。
第13章介紹云原生應用的靜態(tài)編譯和部署實例,側重云服務平臺的部署和性能比較。
第14章介紹用Java實現JVMTI Agent的實例,側重Substrate VM框架對JVMTI編程的支持。
第15章介紹對native image的調試支持,靜態(tài)編譯后的Java程序已經是本地程序,不再支持原先的Java調試方式,而只能通過GDB調試。本章介紹如何用GDB調試native image程序。
林子熠 博士,阿里巴巴集團技術專家,曾任華為技術有限公司高級軟件開發(fā)工程師。
專注于Java靜態(tài)編譯方向,主要負責Java靜態(tài)編譯技術在阿里巴巴生態(tài)中的應用,并以落地實踐中發(fā)現的問題為切入點,向GraalVM社區(qū)貢獻了多項重要特性,是GraalVM社區(qū)建設的積極參與者;曾作為核心人員,負責將華為方舟編譯器前端的Java字節(jié)碼和Art Dex字節(jié)碼轉換為方舟中間語言的開發(fā)工作;曾受邀作為上海JUG2019報告嘉賓和北京QCon2020報告嘉賓,介紹GraalVM靜態(tài)編譯技術;曾在編譯器領域國際學術會議CGO 2021的Graal專項論壇中報告對Java序列化特性的靜態(tài)編譯支持實現。
【部分 從解釋執(zhí)行到靜態(tài)編譯:Java的編譯發(fā)展之路】
第1章 Java靜態(tài)編譯技術的誕生2
1.1 Java程序的運行生命周期3
1.1.1 初始化4
1.1.2 程序預熱5
1.2 冷啟動問題8
1.3 初識Java靜態(tài)編譯技術11
1.3.1 什么是Java靜態(tài)編譯11
1.3.2 靜態(tài)編譯的優(yōu)勢12
1.3.3 靜態(tài)編譯的局限性13
1.4 小結15
第2章 Java靜態(tài)編譯的業(yè)界實現16
2.1 Oracle GraalVM16
2.1.1 GraalVM是什么17
2.1.2 GraalVM靜態(tài)編譯優(yōu)點19
2.1.3 GraalVM靜態(tài)編譯缺點20
2.1.4 GraalVM發(fā)展分析21
2.2 華為方舟編譯器22
2.3 小結24
第3章 GraalVM整體結構25
3.1 子項目與組件25
3.2 GraalVM編譯系統工具mx29
3.3 在IDE中打開GraalVM32
3.4 小結33
第4章 從Java程序到本地代碼:靜態(tài)編譯應用流程34
4.1 獲取GraalVM JDK35
4.1.1 下載發(fā)布版35
4.1.2 下載Docker鏡像37
4.2 從源碼編譯37
4.2.1 編譯準備37
4.2.2 編譯38
4.3 獲取依賴庫40
4.4 預執(zhí)行目標應用程序41
4.5 靜態(tài)編譯目標應用程序43
4.5.1 命令行模式編譯43
4.5.2 配置文件模式45
4.5.3 Maven插件模式46
4.5.4 Gradle插件模式47
4.6 靜態(tài)編譯Java程序實例48
4.6.1 靜態(tài)編譯HelloWorld49
4.6.2 靜態(tài)編譯Spring Boot應用實例50
4.7 小結52
【第二部分 靜態(tài)編譯實現原理】
第5章 Substrate VM靜態(tài)編譯框架54
5.1 靜態(tài)編譯啟動器55
5.2 靜態(tài)編譯實現流程57
5.2.1 類載入59
5.2.2 準備60
5.2.3 靜態(tài)分析61
5.2.4 全局構建63
5.2.5 編譯64
5.2.6 生成image65
5.2.7 寫文件65
5.3 Substrate VM運行時支持67
5.3.1 內存管理67
5.3.2 系統信號處理機制69
5.4 小結70
第6章 Feature機制71
6.1 Feature機制概覽71
6.2 Feature管理73
6.2.1 注冊與調用Feature73
6.2.2 Feature依賴74
6.3 Feature影響編譯流程75
6.3.1 Feature函數的入參回調75
6.3.2 訪問ImageSingletons單例庫76
6.4 GraalFeature實現靜態(tài)編譯優(yōu)化77
6.4.1 GraalVM編譯器基礎知識77
6.4.2 擴展lowering79
6.4.3 注冊圖的擴展插件79
6.5 Feature接口函數80
6.6 小結82
第7章 編譯時替換機制83
7.1 替換機制在Substrate VM中的應用84
7.2 基于注解的替換85
7.2.1 替換類85
7.2.2 替換枚舉類型87
7.2.3 替換函數88
7.2.4 替換構造函數89
7.2.5 替換類中的域90
7.2.6 替換類的靜態(tài)初始化函數92
7.3 實現原理93
7.3.1 替換機制責任鏈93
7.3.2 確定待替換元素集合96
7.3.3 自定義替換內容98
7.4 小結98
第8章 類提前初始化優(yōu)化100
8.1 Java中的類初始化100
8.2 編譯時的類初始化101
8.2.1 類提前初始化的性能分析102
8.2.2 類提前初始化的安全性分析103
8.3 優(yōu)化實現原理106
8.3.1 早期階段分析107
8.3.2 中期階段分析109
8.3.3 后期階段分析111
8.4 手動設置類初始化時機112
8.5 小結113
第9章 反射的實現與優(yōu)化114
9.1 反射在傳統Java中的實現115
9.2 基于配置的支持119
9.2.1 反射配置文件119
9.2.2 配置局限性121
9.3 Substrate VM的反射實現122
9.3.1 解析配置并注冊反射信息123
9.3.2 反射函數常量折疊優(yōu)化124
9.3.3 函數反射調用過程優(yōu)化125
9.4 其他類似動態(tài)特性的支持126
9.4.1 JNI調用127
9.4.2 動態(tài)代理127
9.4.3 資源訪問128
9.4.4 序列化特性129
9.5 小結129
第10章 序列化131
10.1 序列化特性的JDK原生實現131
10.1.1 序列化/反序列化基本流程132
10.1.2 序列化中的靜態(tài)編譯不友好特性133
10.2 靜態(tài)編譯的序列化實現136
10.2.1 解決動態(tài)類加載問題136
10.2.2 解決new抽象類問題138
10.2.3 靜態(tài)初始化函數檢查139
10.3 局限性139
10.4 小結141
第11章 跨語言編程:用Java語言編寫共享庫142
11.1 樣例項目cinterfacetutorial 143
11.2 共享庫的Java實現源碼解析145
11.2.1 聲明共享庫上下文145
11.2.2 實現C基本數據結構146
11.2.3 實現C的結構體繼承149
11.2.4 暴露共享庫API149
11.2.5 直接調用C函數152
11.2.6 共享庫函數的返回值153
11.3 靜態(tài)編譯JNI共享庫153
11.3.1 JNIDemo項目組織結構153
11.3.2 JNI庫API函數的聲明155
11.3.3 JNI函數編程基本過程156
11.3.4 JNI函數參數傳入String157
11.3.5 自定義JNI函數指針類型158
11.3.6 調用Java函數159
11.4 小結160
第12章 CLibrary機制161
12.1 isolate161
12.1.1 錯誤的多線程調用:簡單復用isolate162
12.1.2 正確的多線程調用:為每個線程新建isolate163
12.1.3 正確的多線程調用:映射線程與isolate164
12.2 WordBase接口系統165
12.3 注解系統167
12.3.1 @CContext注解167
12.3.2 @CEntryPoint注解172
12.3.3 @InvokeCFunctionPointer注解173
12.4 正確釋放內存173
12.5 小結175
【第三部分 靜態(tài)編譯實戰(zhàn)】
第13章 靜態(tài)編譯Serverless應用到阿里云函數計算平臺178
13.1 阿里云函數計算平臺178
13.2 靜態(tài)編譯基于Micronaut的Spring-Boot示例項目179
13.3 部署到阿里云180
13.4 性能比較180
13.5 小結182
第14章 native-image-agent的實現183
14.1 native-image-agent與JVMTI183
14.2 實現靜態(tài)編譯的JVMTI Agent185
14.3 native-image-agent的可用選項188
14.4 小結190
第15章 調試191
15.1 編譯debug版本的native image191
15.2 使用GDB調試native image193
15.2.1 啟動GDB194
15.2.2 增加函數斷點194
15.2.3 GDB TUI分屏界面195
15.2.4 單步調試197
15.2.5 查看Java對象的值197
15.3 小結199