本书作者是阿里静态编译研究团队的核心成员,曾任华为高级工程师,一直从事静态编译技术的研究与落地。本书得到业界多位知名专家的鼎力推荐,是深度、系统研究Java静态编译技术的鲜见资料。
阅读本书,读者可以了解Java静态编译技术的特点、实现机制、优缺点等,以将其应用到自己的业务实践中。
【本书分为三部分】:
第1部分(第1~4章)首先概要介绍Java编译器的演进过程,然后介绍Java静态编译的基本原理、总体能力和发展前景,主要向读者说明Java静态编译技术是什么、业界主流实现方案、GraalVM结构及静态编译应用的流程。
第2部分(第5~12章)详细介绍GraalVM如何实现Java的静态编译,着重介绍如何支持现有Java特性,涵盖静态编译的总体流程、重要机制(扩展、替换、类提前初始化优化机制)、动态特性(反射和序列化)实现、跨语言编程能力等。读者可以掌握GraalVM对Java静态编译技术的具体实现原理,了解动态的Java特性是如何被静态化支持的,洞悉静态编译本质。
第3部分(第13~15章)主要关注GraalVM的实践应用,介绍如何将传统Java应用编译为二进制可执行文件或静态库文件并部署上云,以及与传统的Java程序完全不同的编译调试技术。读者可以获悉静态编译实践中常见问题的解决方案,以更快拥有静态编译技术的应用实践能力。
1)多名专家联袂推荐:北京大学计算机科学技术系主任胡振江教授、阿里蔡景现(多隆)、华为方舟编译器总架构师叶寒栋、GraalVM核心开发人员郑雨迪联袂推荐。
2)阿里资深专家撰写:作者林子熠阿里静态编译研究团队的核心成员,曾任华为高级工程师,一直从事静态编译技术的研究与落地。
3)Java应用性能提升制胜法宝:通过静态编译技术实现Java应用冷启动,实现性能质的飞跃。
4)全方位落地指导:深入原理,给出具体应用与调试技术,指导读者做好平稳落地工作。
【为什么写作本书】
Java语言可谓程序语言界的常青藤,自1996年诞生以来,长期在受欢迎的编程语言排行榜中占据领先地位。除了语言本身的优秀特性之外,Java语言持续演进、不断发展也是它能够保持长盛不衰的重要原因。
近年来,随着云原生浪潮的兴起,越来越多的应用被部署在了云厂商的云服务环境中,以计算资源的形式为用户提供服务。在这种趋势下,应用本身越来越小,对跨平台的需求越来越弱(因为平台问题已经由云厂商解决了),但是对应用快速启动、即起即用和高性能执行的需求越来越强。Java程序的冷启动问题在这种场景下就显得格外突出,成为开发人员在选择编程语言时的主要减分项。根据著名的TIOBE编程语言流行趋势索引统计,Java语言的市场占有率从2016年1月的21.4%跌至2021年8月的10%,在C和Python之后,排名第三。
难道使用Java语言就只能忍受冷启动问题吗?Java社区和工业界一直在探索冷启动问题的解决之道,希望使用Java的用户在享受Java丰富生态的同时,还能获得良好的启动性能。比如OpenJDK提出的AppCDS(Application Class Data Sharing)技术,可以将已经加载的类的元数据导出到文件,在下次启动时直接从文件导入这些数据,无须再次经过类的解析和加载等过程,由此削减启动时的类加载开销。但是,因为Java的冷启动问题的根源在于JVM本身,所以在JVM之上做的各种优化的效果都是有限的,难以实现质的飞跃。
从根本上审视Java冷启动问题可以发现,启动一个Java程序并让它达到性能的峰值需要经过VM初始化应用程序初始化字节码解释执行JIT编译热点函数执行JIT编译后的本地代码(native code)等环节,且不论在这些环节上能够做出何种优化,单这么长的一条链路已足以说明冷启动问题之复杂、难解。如果不能打破这条链路,而只是在各个环节上进行优化,恐怕很难达到理想的效果。那么是否能够打破这条长链,越过中间环节直达后一步,像C语言一样直接将Java代码编译为本地代码执行呢?
答案是肯定的,这就是本书要为读者展现的Java静态编译技术。Oracle公司推出的开源高性能多语言运行平台项目GraalVM,打造了一个包括静态编译器和轻量级运行时的Java静态编译框架,可以将Java程序从字节码直接编译为本地可执行应用程序。与在JVM下执行相比,静态编译后的Java程序的启动速度能够提升两个数量级,完全解决了冷启动问题,实现了Java应用程序启动性能的质的突破。目前关于GraalVM静态编译的大多数资料都是开发团队发布的技术文档、博客和GitHub上的开发相关问题讨论,而缺少系统全面性的资料介绍,尤其缺乏中文资料。因此国内的广大程序开发者和技术爱好者对其并不了解。本书旨在填补这方面的空白,使读者能够系统性了解并掌握GraalVM静态编译技术。
【本书特色】
本书将为读者详细解释GraalVM中的Java静态编译技术,不仅带你了解GraalVM的静态编译框架的使用方法,更重要的是向你介绍其背后的实现原理。有兴趣的读者在阅读完本书后可以独立阅读甚至修改GraalVM中的源码,并向社区提出自己的功能改进建议或Bug修复的补丁,帮助GraalVM更好地发展。本书侧重介绍GraalVM静态编译框架和运行时的应用与原理,而不太涉及编译部分。
原因如下:其一,GraalVM的静态编译中使用的编译器并不专用于Java静态编译,如可用于代替HotSpot的C2编译器,其内容博大精深,足以单独成书,所以不会过多阐述;其二,Java静态编译的难点并不在于编译本身,而是在于确定编译的范围以及对JVM原本动态运行时的改造适配等,因为JVM的实时编译器早已实现对Java字节码的编译。
【如何阅读本书】
本书分为三部分,分别从应用、实现原理和具体实例三个方面进行阐述。
部分(第1~4章)从整体上介绍GraalVM项目及其静态编译子项目Substrate VM。
第1章向读者介绍Java静态编译产生的技术原因Java冷启动问题的产生和由来。
第2章首先对GraalVM做概要介绍,然后分别介绍Substrate VM和方舟编译器这两种实现方案,并对比它们的技术特点。
第3章向读者介绍Oracle GraalVM项目的整体结构。
第4章介绍使用GraalVM静态编译Java应用的详细步骤。
第二部分(第5~12章)主要介绍GraalVM中静态编译框架子项目Substrate VM的实现原理。
第5章介绍Substrate VM静态编译框架的实现与总体流程。
第6章介绍Substrate VM中的功能扩展机制Feature机制,框架中的各个具体功能点都是通过该机制实现的。
第7章介绍编译时的程序元素替换功能Substitution机制,该机制实现了无侵入性的程序元素替换能力,在静态编译框架的运行时实现中有基础性的地位。
第8章介绍Substrate VM的类提前初始化优化技术,该技术将符合条件的类在编译时初始化,不但节省了运行时初始化的开销,而且无须分析已经运行过的类初始化函数,因此降低了编译时的静态分析开销。
第9章和第10章分别介绍两种具有代表性的Java动态特性反射和序列化的静态化实现过程。
第11章和第12章介绍Substrate VM的跨语言编程能力。
第三部分(第13~15章)通过两个实例介绍Java静态编译技术的实践,并在后介绍程序在静态编译后的产物native image的调试方法。
第13章介绍云原生应用的静态编译和部署实例,侧重云服务平台的部署和性能比较。
第14章介绍用Java实现JVMTI Agent的实例,侧重Substrate VM框架对JVMTI编程的支持。
第15章介绍对native image的调试支持,静态编译后的Java程序已经是本地程序,不再支持原先的Java调试方式,而只能通过GDB调试。本章介绍如何用GDB调试native image程序。
林子熠 博士,阿里巴巴集团技术专家,曾任华为技术有限公司高级软件开发工程师。
专注于Java静态编译方向,主要负责Java静态编译技术在阿里巴巴生态中的应用,并以落地实践中发现的问题为切入点,向GraalVM社区贡献了多项重要特性,是GraalVM社区建设的积极参与者;曾作为核心人员,负责将华为方舟编译器前端的Java字节码和Art Dex字节码转换为方舟中间语言的开发工作;曾受邀作为上海JUG2019报告嘉宾和北京QCon2020报告嘉宾,介绍GraalVM静态编译技术;曾在编译器领域国际学术会议CGO 2021的Graal专项论坛中报告对Java序列化特性的静态编译支持实现。
【部分 从解释执行到静态编译:Java的编译发展之路】
第1章 Java静态编译技术的诞生2
1.1 Java程序的运行生命周期3
1.1.1 初始化4
1.1.2 程序预热5
1.2 冷启动问题8
1.3 初识Java静态编译技术11
1.3.1 什么是Java静态编译11
1.3.2 静态编译的优势12
1.3.3 静态编译的局限性13
1.4 小结15
第2章 Java静态编译的业界实现16
2.1 Oracle GraalVM16
2.1.1 GraalVM是什么17
2.1.2 GraalVM静态编译优点19
2.1.3 GraalVM静态编译缺点20
2.1.4 GraalVM发展分析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程序到本地代码:静态编译应用流程34
4.1 获取GraalVM JDK35
4.1.1 下载发布版35
4.1.2 下载Docker镜像37
4.2 从源码编译37
4.2.1 编译准备37
4.2.2 编译38
4.3 获取依赖库40
4.4 预执行目标应用程序41
4.5 静态编译目标应用程序43
4.5.1 命令行模式编译43
4.5.2 配置文件模式45
4.5.3 Maven插件模式46
4.5.4 Gradle插件模式47
4.6 静态编译Java程序实例48
4.6.1 静态编译HelloWorld49
4.6.2 静态编译Spring Boot应用实例50
4.7 小结52
【第二部分 静态编译实现原理】
第5章 Substrate VM静态编译框架54
5.1 静态编译启动器55
5.2 静态编译实现流程57
5.2.1 类载入59
5.2.2 准备60
5.2.3 静态分析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实现静态编译优化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 替换类的静态初始化函数92
7.3 实现原理93
7.3.1 替换机制责任链93
7.3.2 确定待替换元素集合96
7.3.3 自定义替换内容98
7.4 小结98
第8章 类提前初始化优化100
8.1 Java中的类初始化100
8.2 编译时的类初始化101
8.2.1 类提前初始化的性能分析102
8.2.2 类提前初始化的安全性分析103
8.3 优化实现原理106
8.3.1 早期阶段分析107
8.3.2 中期阶段分析109
8.3.3 后期阶段分析111
8.4 手动设置类初始化时机112
8.5 小结113
第9章 反射的实现与优化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 反射函数常量折叠优化124
9.3.3 函数反射调用过程优化125
9.4 其他类似动态特性的支持126
9.4.1 JNI调用127
9.4.2 动态代理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 序列化中的静态编译不友好特性133
10.2 静态编译的序列化实现136
10.2.1 解决动态类加载问题136
10.2.2 解决new抽象类问题138
10.2.3 静态初始化函数检查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 静态编译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
【第三部分 静态编译实战】
第13章 静态编译Serverless应用到阿里云函数计算平台178
13.1 阿里云函数计算平台178
13.2 静态编译基于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 实现静态编译的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