JVM 之旅——编译 OpenJDK

最近开始学习 JVM,跟着『深入理解 Java 虚拟机』一起趟坑,记下这篇笔记。以下内容都是在 64 位的 Ubuntu 18.04 的环境中进行操作的结果。由于书中写的是 OpenJDK 7 的相关内容,所以先熟悉书中相应版本的内容,之后再研究新版本的特性。

前期准备

首先当然是获取 OpenJDK 的源码,获取源码有两种方式:第一种是直接从官网下载打包好的源码,下载完解压到本地就可以了,但是,这种方式下载,不知道是我没找对地方还是官网真的改的一言难尽,很多超链接都 404 了,所以这种方式不是很推荐,这里还是放上我找到的下载地址;另一种方式就是版本控制工具下载,不过这个版本控制工具不是 Git、SVN 这类比较常见的,而是 Mercurial。以下命令可以安装 Mercurial:

1
sudo apt-get install mercurial

安装好 Mercurial 就可以通过 OpenJDK 的仓库地址将源码拉下来了:

1
2
3
hg clone http://hg.openjdk.java.net/jdk7/jdk7 OpenJDKDirName
cd OpenJDKDirName
sh ./get_source.sh

其他版本的仓库地址官方文档Documentation 部分找到,下载过程可能会有点慢,等着就好了,下载完了一定要打开 OpenJDKDirName/README-builds.html 看看,或者上述 Documentation 中各个版本的 README 看看,内容是一样的,这份文档详细的介绍了编译 OpenJDK 需要注意的事项以及编译的步骤。编译 OpenJDK 需要注意的依赖要求及我在配置过程遇到的坑记录如下:

  • Bootstrap JDK: 这个 JDK 是指上一个 release 版本的 JDK 作为基础,比如编译 OpenJDK 7 就需要 OpenJDK 6,并且需要将 OpenJDK 的路径配置为 ALT_BOOTDIR 的值

  • Ant: Ant 需要 1.7.1 以上的版本。这个需要注意不能使用 1.10.x 以上的版本,因为 1.10.x 以上的版本需要 JDK 8 的支持,所以这个建议到Ant官网手动下载合适的版本,或者直接从 README-builds 提供的链接下载,手动安装需要配置环境变量 ANT_HOME 为 Ant 的路径,并且将 ${ANT_HOME}/bin 配置到 PATH

  • FreeType 2: 需要 2.3 以上的版本。这里遇到个奇怪的 bug,我安装了 2.10 版本的 FreeType 2,最后检查的时候非说 2.10 版本低于 2.3,无奈最后从官网下载了 2.9 配置好才没有报错。从 FreeType 2 官网下载好源码好按照其中的 README 文档进行编译安装,最终库文件位于 /usr/local/lib 下,对应需要配置的变量为 ALT_FREETYPE_LIB_PATH;头文件位于 /usr/local/include 下,对应需要配置的变量为 ALT_FREETYPE_HEADERS_PATH

  • ALSA: 这个依赖通过 sudo apt-get install libasound2-dev 来安装即可

依赖安装完成后对应需要配置环境变量,除了以上各依赖对应的环境变量以外,参考书中内容整理了配置环境变量的脚本 variable_script.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
# set variable before build open jdk
# 设置语言选项,以防编译时出现 NullPointorException
export LANG=C
# 这是 Bootstrap JDK 对应的环境变量
export ALT_BOOTDIR=~/Software/Java/jdk1.6.0_45
# 允许编译时自动下载依赖
export ALLOW_DOWNLOADS=true
# 比较本次 build 出来的映像与先前版本的差异。
export SKIP_COMPARE_IMAGES=true
# 使用预编译头文件,可以加快编译速度
export USE_PRECOMPILED_HEADERS=true
# 要编译的内容
export BUILD_LANGTOOLS=true
export BUILD_HOTSPOT=true
export BUILD_JDK=true
# 避开 javaws 和浏览器 Java 插件之类部分的编译
export BUILD_DEPLOY=false
# 不编译出安装包
export BUILD_INSTALL=false
# 编译结果所存放的路径,不设置的话编译结果存放于 OpenJDKDirName/build/bsd
# bsd 指不同系统内核
export ALT_OUTPUTDIR=...
# 这两个环境变量必须去掉,不然编译前的检测会通不过
unset JAVA_HOME
unset CLASSPATH

编译之前运行 source variable_script.sh 即可让以上环境变量生效,之后使用 make sanity 检测变量配置情况,如果最终出现 Sanity check passed 则表示检测通过了,可以输入 make 命令进行编译了,如果出现错误提示,根据提示解决相应问题就行了。当然这些命令都是在 OpenJDKDirName 目录下运行的。最终会在终端看到日志输出,如果需要保存日志的话,执行 make 命令的时候利用管道将日志输出到文件即可,将 make 命令换成 make 2>&1 | tee <destination>/build.log。整个编译过程大概持续了半小时,静静等着就好了,如果中途出错,它会提示你的。

编译过程遇到的坑

由于编译过程没有记录日志,印象中除了依赖部分提到的问题,另外遇到的坑就是编译过程中报出 cc1plus all warnings being treated as errors ubuntu,这是因为编译检测过程比较严格,将警告当成错误处理阻止继续编译,解决办法是修改配置文件 ./hotspot/make/linux/makefiles/gcc.make,将 WARNING_ARE_ERRORS 变量改为

1
WARNING_ARE_ERRORS = -Wno-error

编译结束后,如果看到类似以下的信息,说明 OpenJDK 编译成功了。

1
2
3
4
5
6
7
8
9
10
11
12
#-- Build times ----------
Target debug_build
Start 2019-04-12 00:35:21
End 2017-04-12 00:38:38
00:01:23 corba
00:01:10 hotspot
00:00:14 jaxp
00:06:06 jaxws
00:08:31 jdk
00:18:03 langtools
00:03:17 TOTAL
-------------------------

编译成功后进入编译结果存放的路径,下面有 ./j2sdk-image 进入后就是平时使用的 JDK 了,将其配置到 JAVA_HOME 环境变量就可以正常使用 JDK 了。使用 java -version 还可以看到用户名的机器名,比如我的就是:

1
2
3
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-crazypudding_2019_04_12_20_39-b00)
OpenJDK 64-Bit Server VM (build 24.95-b00, mixed mode)

至此,OpenJDK 的编译就告一段落了。既然我们是研究 JVM,当然我们的主要目标是 Hotspot,而且调试虚拟机后的改动也不用编译整个 OpenJDK 了,那继续单独编译 Hotspot。

单独编译 Hotspot 虚拟机

编译虚拟机其实也很简单,不过书中写的不是特别明了,所以还是费了我不少时间,这里就简单记录下完整的过程就好了。

很明显,Hotspot 是由 C 编写的,由 Makefile 管理编译过程,所以可以将 Hotspot 作为单独模块拿出来编译,使用的就是 OpenJDKDirName/hotspot/make 目录下的 Makefile 文件,记得终端需要切换到 OpenJDKDirName/hotspot/make 目录下,其他参数与编译 OpenJDK 时保持一致就 ok,如果需要重新配置环境变量的话,使用之前编写的 source variable_script.sh 就可以了。

1
2
cd OpenJDKDirName/hotspot/make
make

期间可能会因为 Bootstrap JDK 是 64 位的终端编译,查看日志会发现提示 JAVA_HOME must point to 32 bit 之类的,解决方法就是下载一个 32 位的 Bootstrap JDK 并设置好环境变量 ALT_BOOTDIR 即可。

编译过程大概也是半小时左右,耐心等待就好了,输出结果存放在之前配置的结果存放路径下的 ./hotspot/outputdit/bsd_compiler2 目录中,由于没有制定编译版本,所以默认是 product,进入 product 下修改下 env.sh 文件,新增一个环境变量如下:

1
2
LD_LIBRARY_PATH=.:${JAVA_HOME}/jre/ilb/amd64/native_threads:${JAVA_HOME}/jre/lib/amd64
export LD_LIBRARY_PATH

配置完后在当前路径执行以下命令启动虚拟机,输出版本号:

1
2
. ./env.sh
./gamma -version

会看到如下结果:

1
2
3
4
Using java runtime at: /home/crazypudding/Documents/jdk7u/build/linux-amd64/j2sdk-image/jre
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-crazypudding_2019_04_12_20_39-b00)
OpenJDK 64-Bit Server VM (build 24.95-b00, mixed mode)

至此,Hotspot 虚拟机也完成编译了~

呼啦啦...