写 Java 这么久了,来编译个 JDK 玩玩儿吧

你每天写的 Java 代码都需要 JDK 的支持,都要跑在 JVM 上,难道你就不好奇 JDK 长什么样子吗。好奇,就来编译并实现一个自己的 JDK 吧。

本次编译环境 macOS 10.12,编译的是 JDK 11 版本。

安装 OpenJDK 11

编译 OpenJDK 需要先在机器上安装 OpenJDK 10 或者 OpenJDK 11,作为 Boot JDK。 先安装 openJDK 11 编译需要,可以到 adoptopenjdk 网站去下载。

pkg 格式安装

进入页面 https://adoptopenjdk.net/index.html?variant=openjdk11&jvmVariant=hotspot 直接下载下载,然后双击就可以完成安装了。

tar.gz 格式安装

1、进入页面 https://adoptopenjdk.net/installation.html?variant=openjdk11&jvmVariant=hotspot#x64_mac-jdk 下载 tar.gz 包

2、解压

代码语言:javascript
复制
tar -xf OpenJDK11U-jdk_x64_mac_hotspot_11.0.5_10.tar.gz

解压后是一个 macOS 包,可通过右键->显示包内容查看里面的文件。

3、加入环境变量 PATH 中,当然如果你使用其他版本的 JDK 作为开发使用,请忽略这一步。

代码语言:javascript
复制
export PATH=$PWD/jdk-11.0.5+10/Contents/Home/bin:$PATH

4、macOS JDK 默认目录在/Library/Java/JavaVirtualMachines,把第 2 步解压的内容放到此目录下,之后编译的过程中会在这个目录下查找 JDK 10 或 JDK 11。

下面是我本地的目录结构,有 7 、8、11 这三个版本,开发时候还是默认使用 8 的。

安装 xcode

实际上我们需要的不是 xcode,而是 LLVM 的编译命令 clang。当然你可以单独安装 LLVM,但限于此篇是写给 Java 开发者的,安装 xcode 是最简单的版本。

我本地是很早之前安装的 xcode 8.1,编译起来是没问题的,如果你用的是比较新的版本,应该也不会出现什么问题,可以亲自试一试。

开始编译

1、下载 OpenJDK 11 源码

OpenJDK 的源码放在了网站 http://hg.openjdk.java.net/ 上,我们要下载的 JDK11 目录在 http://hg.openjdk.java.net/jdk-updates/jdk11u/。

进入页面后,先点击左侧的 browse,再选择一种压缩格式下载。

当然还可以用 hg 命令 clone 到本地,使用 hg 需要安装 mercurial,如果网速不好或者不稳定,建议不要使用这种方式。

代码语言:javascript
复制
hg clone https://hg.openjdk.java.net/jdk/jdk11/

2、解压源码包

将你刚刚下载的压缩包解压,请解压到一个全英文目录下,不要使用中文,减少编译时带来的麻烦。

3、configure

进入上一步解压后的目录,执行如下命令。

代码语言:javascript
复制
sh configure --with-target-bits=64 --enable-ccache --with-jvm-variants=server  --with-boot-jdk-jvmargs="-Xlint:deprecation -Xlint:unchecked" --disable-warnings-as-errors --with-debug-level=slowdebug 2>&1 | tee configure_mac_x64.log

执行这个命令的前提是我已经将 OpenJDK 11 放到了 /Library/Java/JavaVirtualMachines目录下。

如果不放到这个目录下,也是可以的,需要额外指定参数

代码语言:javascript
复制
--with-boot-jdk=OpenJDK 目录

如果出现如下输出,说明这一步就正常了。

4、make

正式开始编译了,使用 make 命令即可。

代码语言:javascript
复制
make

首次编译会比较慢,我的是 MacBook Pro i5 8G 的那款,大概编译了 10 几分钟吧。当出现如下输出的时候,说明编译成功。

代码语言:javascript
复制
Finished building target 'default (exploded-image)' in configuration 'macosx-x86_64-normal-server-slowdebug'

编译好之后,会在当前目录出现 build 目录,进去之后,看到有个 macosx-x86_64-normal-server-slowdebug 就是最终的目录。

IDEA 中配置使用编译好的 JDK

1、打开 IDEA ,找到 File->Project Structure。

2、添加一个 JDK

3、选择上面源码编译好的 jdk

4、最后启动项目的时候指定这个 JDK 就可以了。

用 CLion 调试

1、打开 CLion ,导入项目,选择下载的源码所在位置的 src 目录。

2、配置 Debug Configurations,选择 Executable 为编译好的 java 可执行程序,在 bin 目录下,并且移除 Build 设置。

Program arguments 设置为 -version,也可以设置其他的。设置为 -version 的意思是 java -version。

3、最后在源码中打个断点,比如 jni.cpp 或 thread.cpp 中,然后点击 debug ,就可以调试啦。

打造自己的 JDK

标题说的有点儿悬,打造自己的 JDK 哪儿有那么容易,况且还确实没那个实力。这里就是介绍一种思路,比如有些时候,我们调试 Java 代码最后发现走到了 JVM 层,这种情况下,我们就跟不进去了。执行到 JVM 层之后,里面的各种变量是怎么变换的我们就不知道了。这时候,我们找到 JVM 对应的代码稍微改一下,比如加个 printf 输出一下参数值就可以清晰的看出来了。

修改 JDK 代码

我在打开的 CLion 中找到了 java.c 文件的 JavaMain(void * _args) 方法,在里面加了一行打印代码,就勉强算实现了自己的 JDK 吧(微笑脸)。

万里长征第一步嘛,别的不重要,留下脚印儿才是关键。

代码语言:javascript
复制
printf("古时的风筝 JDK \n");

重新编译修改后的源码

修改之后,在终端中进入到源码目录的根目录,然后执行 make 命令。

因为之前已经编译过了,所以再次执行 make 是进行的增量编译,所以速度很快。

好了,见证奇迹的时刻到了

我们之前已经在 IDEA 中添加了编译好的 JDK,并且指定给了一个项目。仅为测试,代码如下。

代码语言:javascript
复制
public static void main(String[] args){
    System.out.println("hello jvm");
}

当我们运行这个项目的时候,如果是平常的 JDK,会在控制台输出 hello jvm ,对不对。

可是,现在指定的不是平常的 JDK ,是被我加持过的 JDK 。

开始运行,输出的结果如下,看到没,刚刚加上的那行代码起作用了。

风筝说

真正能做到 JDK 定制开发的人并不多,我也完全没这个实力。但是每个 Java 开发者都编译一下 JDK 源码,翻一翻代码还是很有必要的。毕竟,我们每天写的代码都需要 JDK 的支持,都要跑在 JVM 上,我们就不好奇它们长成什么模样吗。

另外,这也可能为我们日常解决问题提供一种思路。有人说,最好的老师就是搜索引擎,大多数情况下是没错,但有的时候最好的方式往往就是看一眼源码。

为什么有的人解决问题的速度快,有些看似不能解决的问题放到大牛手里就能很快解决。有时候就是解决问题的维度不一样,人家是在三维的世界里,你却一直在二维的平面里转圈圈,比方说遇到程序问题,只能分析 Java 层面的问题这就是二维,进到 JDK、JVM 源码那就是进到的三维。维度高了,角度变了,解决问题的可能性和方式也就多了。这就好比三体里高等文明利用二向箔进行打击,完全不在一个体量下。

赶紧行动吧,编译一个你自己的 JDK。