想要窥探Java虚拟机内部的实现原理,最直接的一条路径就是编译一套自己的JDK,通过阅读和 跟踪调试JDK源码来了解Java技术体系的运作,虽然这样门槛会比阅读资料更高一点,但肯定也会比 阅读各种文章、书籍来得更加贴近本质。此外,Java类库里的很多底层方法都是Native的,在了解这些 方法的运作过程,或对JDK进行Hack(根据需要进行定制微调)的时候,都需要有能自行编译、调试 虚拟机代码的能力。

现在网络上有不少开源的JDK实现可以供我们选择,但毫无疑问OpenJDK是使用得最广泛的 JDK,我们也将选择OpenJDK来进行这次编译实战。

1.1 获取源码 jdk 12

注意: jdk11 之后, openjdk和oraclejdk代码实质上已经达到了完全一致.

源码下载地址 jdk12

https://hg.openjdk.java.net/jdk/jdk12/archive/06222165c35f.ziphg.openjdk.java.net

但是由于下载太慢了(500多兆,下载速度3KB/s)

所以去gitee上搜索了一下 下载gitee上的源码镜像并下载(2min下载完)


isspark/OpenJDK12gitee.com

1.2 系统要求

建议尽量在Linux或者MacOS上构建OpenJDK,这两个系统在准备构建工具链和依 赖项上要比在Windows或Solaris平台上要容易许多,本篇实践中作者将以macOS Catalina 10.15.4为平台进行 构建

在本次编译中采用的是64位操作系统,默认参数下编译出来的也是64位的OpenJDK,如果需要编 译32位版本,笔者同样推荐在64位的操作系统上进行,理由是编译过程可以使用更大内存(32位系统 受4G内存限制),通过编译参数(--with-target-bits=32)来指定需要生成32位编译结果即可

在官方 文档上要求编译OpenJDK至少需要2~4GB的内存空间(CPU核心数越多,需要的内存越大),而且 至少要6~8GB的空闲磁盘空间,不要看OpenJDK源码的大小只有不到600MB,要完成编译,过程中 会产生大量的中间文件,并且编译出不同优化级别(Product、FastDebug、SlowDebug)的HotSpot虚 拟机可能要重复生成这些中间文件,这都会占用大量磁盘空间。

对系统环境的最后一点建议是,所有的文件,包括源码和依赖项目,都不要放在包含中文的目录 里面,这样做不是一定会产生不可解决的问题,只是没有必要给自己找麻烦。

1.3 构建编译环境

在MacOS上构建OpenJDK编译环境相对简单,对于MacOS,需要MacOS X 10.13版本以上,并安装好最新版本的XCode和Command Line Tools for XCode(在Apple Developer网站上可以免 费下载),这两个SDK提供了OpenJDK所需的CLang编译器以及Makefile中用到的其他外部命令,最好再安装一下homebrew,后面如果部分缺省部分组件,使用homebrew安装十分简单.

最后,假设要编译大版本号为N的JDK,我们还要另外准备一个大版本号至少为N-1的、已经编译 好的JDK,这是因为OpenJDK由多个部分(HotSpot、JDK类库、JAXWS、JAXP……)构成,其中一 部分(HotSpot)代码使用C、C++编写,而更多的代码则是使用Java语言来实现,因此编译这些Java代 码就需要用到另一个编译期可用的JDK,官方称这个JDK为“Bootstrap JDK”。编译OpenJDK 12时, Bootstrap JDK必须使用JDK 11及之后的版本。在mac中使用以下命令安装OpenJDK 11:



brew cask install adoptopenjdk



1.4 进行编译

1.4.1 编译依赖检查

通常我们 编译OpenJDK的目的都不仅仅是为了得到在自己机器中诞生的编译成品,而是带着调试、定制化等需 求,这样就必须了解OpenJDK提供的编译参数才行,这些参数可以使用“./ configure --help”命令查询

对它们中最有用的部分简要说明如下:

·--with-debug-level=<level>:设置编译的级别,可选值为release、fastdebug、slowde-bug,越往后进 行的优化措施就越少,带的调试信息就越多。还有一些虚拟机调试参数必须在特定模式下才可以使 用。默认值为release。

·--enable-debug:等效于--with-debug-level=fastdebug。

·--with-jvm-variants=<variant>[,<variant>...]:编译特定模式(Variants)的HotSpot虚拟机,可以 多个模式并存,可选值为server、client、minimal、core、zero、custom。

然后执行下面的编译命令

编译FastDebug版、仅含Server模式的HotSpot虚拟机,命令应为:



=server



注意: configure命令承担了依赖项检查、参数配置和构建输出目录结构等多项职责,如果编译过程中需 要的工具链或者依赖项有缺失,命令执行后将会得到明确的提示,并且给出该依赖的安装命令
注意: 如果提示 permisson 表示权限不够 使用 chmod +x configure 为它添加可执行权限

若出现类似如下提示,表明缺乏组件



Runnable configure script is not present 
Generating runnable configure script at /Users/selton/InstallEnv/jdk12src/openjdk12_source_code_package/build/.configure-support/generated-configure.sh 
Autoconf is not found on the PATH, and AUTOCONF is not set. 
You need autoconf to be able to generate a runnable configure script. 
You might be able to fix this by running 'brew install autoconf'. 
Error: Cannot find autoconf



上面这个错误提示缺乏autoconf组件,使用brew install autoconf即可下载安装

如果上次编译失败了 然后安装了编译失败提示的需要的组件,再次编译的时候,需要先清空上次编译内容
必须先使用“make clean”和“make dist-clean”命令清理目录

如果一切顺利的话,就会收到配置成功的提示,并且输出调试级别,Java虚拟机的模式、特性, 使用的编译器版本等配置摘要信息,如下所示



==================================================== 
A new configuration has been successfully created in 
/Users/selton/InstallEnv/jdk12src/openjdk12_source_code_package/build/macosx-x86_64-server-fastdebug 
using configure arguments '--enable-debug --with-jvm-variants=server'. 
Configuration summary: 
* Debug level: fastdebug 
* HS debug level: fastdebug 
* JVM variants: server 
* JVM features: server: 'aot cds cmsgc compiler1 compiler2 dtrace epsilongc g1gc graal jfr jni-check jvmci jvmti management nmt parallelgc serialgc services shenandoahgc vm-structs' 
* OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64 
* Version string: 12-internal+0-adhoc.selton.openjdk12sourcecodepackage (12-internal) 
Tools summary: 
* Boot JDK: openjdk version "12.0.2" 2019-07-16 OpenJDK Runtime Environment AdoptOpenJDK (build 12.0.2+10) OpenJDK 64-Bit Server VM AdoptOpenJDK (build 12.0.2+10, mixed mode, sharing) (at /Library/Java/JavaVirtualMachines/openjdk12/Contents/Home) 
* Toolchain: clang (clang/LLVM from Xcode 11.2) 
* C Compiler: Version 11.0.0 (at /usr/bin/clang) 
* C++ Compiler: Version 11.0.0 (at /usr/bin/clang++) 
Build performance summary: 
* Cores to use: 4 
* Memory limit: 8192 MB



1.4.2 编译

依赖检查通过后便可以输入

执行命令的位置默认都是在源码主目录下



make images



执行整个OpenJDK编译了,这里“images”是“productimages”编译目标(Target)的简写别名,这个目标的作用是编译出整个JDK镜像

笔者使用的是macos, 8GB内存,全量编译整个OpenJDK 12 大概需近17分钟时间

如果编译的时候发生了如下错误



=== Output from failing command(s) repeated here === 
* For target hotspot_variant-server_libjvm_gtest_objs_test_symbolTable.o: 
/Users/selton/InstallEnv/jdk12src/openjdk12_source_code_package/test/hotspot/gtest/classfile/test_symbolTable.cpp:62:6: error: explicitly assigning value of variable of type 'TempNewSymbol' to itself [-Werror,-Wself-assign-overloaded] 
s1 = s1; // self assignment



因为这个是因为/Users/selton/InstallEnv/jdk12src/openjdk12_source_code_package/test/hotspot/gtest/classfile/test_symbolTable.cpp:里一句代码s1 = s1;报错的,进入源码发现该行代码(在我的理解下)是无用代码,因此注释掉

如果发生了上述编译错误, 注释修改完代码后 需要先 执行清除命令 再编译



#注释test/hotspot/gtest/classfile/test_symbolTable.cpp:62该行s1=s1;代码。 
make clean



编译完成之后,进入OpenJDK源码的“build/配置名称/jdk”目录下就可以看到OpenJDK的完整 编译结果了,把它复制到JAVA_HOME目录,就可以作为一个完整的JDK来使用,如果没有人为设置 过JDK开发版本的话,这个JDK的开发版本号里默认会带上编译的机器名,如下所示:



~/InstallEnv/jdk12src/openjdk12_source_code_package/build/macosx-x86_64-server-fastdebug/jdk/bin                                       ✔  10117  10:44:52
 $ ./java -version
openjdk version "12-internal" 2019-03-19
OpenJDK Runtime Environment (fastdebug build 12-internal+0-adhoc.selton.openjdk12sourcecodepackage)
OpenJDK 64-Bit Server VM (fastdebug build 12-internal+0-adhoc.selton.openjdk12sourcecodepackage, mixed mode)



编译产生的目 录结构以及用途如下所示:



buildtools/:用于生成、存放编译过程中用到的工具
hotspot/:HotSpot虚拟机编译的中间文件 
images/:使用make *-image产生的镜像存放在这里
jdk/:编译后产生的JDK就放在这里
support/:存放编译时产生的中间文件
test-results/:存放编译后的自动化测试结果
configure-support/:这三个目录是存放执行configure、make和test的临时文件



参考文献: 周志明 深入理解java虚拟机 第三版