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