写 Java 这么久,JDK 源码编译过没?编译 JDK 源码踩坑纪实 - 今日头条

本文由 简悦 SimpRead 转码, 原文地址 www.toutiao.com

很多小伙伴们做 Java 开发,天天写 Java 代码,肯定离不开 Java 基础环境: JDK,毕竟我们写好的 Java 代码也是跑在 JVM 虚拟机上。

https://p3.toutiaoimg.com/origin/pgc-image/f24868a9cf084b05b68fba4a366ea3e9?from=pc

很多小伙伴们做 Java 开发,天天写 Java 代码,肯定离不开 Java 基础环境:JDK,毕竟我们写好的 Java 代码也是跑在 JVM 虚拟机上。

一般来说,我们学 Java 之前,第一步就是安装 JDK 环境。这个简单啊,我们一般直接把 JDK 从官网下载下来,安装完成,配个环境变量就可以愉快地使用了。

不过话说回来,对于这个天天使用的东西,我们难道不好奇这玩意儿它到底是怎么由源码编译出来的吗?

带着这个原始的疑问,今天准备大干一场,自己动动呆萌的小手,来编译一个属于自己的 JDK 吧!

对了,本文在开源项目:** https://github.com/hansonwang99/JavaCollection** 中已收录,包含自学编程路线、面试题集合 / 面经、及系列技术文章等,资源持续更新中…

记得之前不是出过一期关于 《JDK 源码阅读环境搭建》 相关的视频以及文章嘛,细心的小伙伴,可能会发现一个很实际的问题

我们将 src.zip 包里的 JDK 源码解压出来,关联到这份源码之后,调试时是可以进,但是我们在加注释的时候却只能在行尾添加,并不能改变原代码的行结构。换句话说,如果在源码中加了跨行的多行注释,则 debug 调试的时候就会出现当前行的运行错位问题,这个有点尴尬了。

当然那个视频的评论区,的确也有几个小伙伴提了这个问题:

https://p3.toutiaoimg.com/origin/pgc-image/15b2b740e7f242d29eda8c312e4a2699?from=pc

https://p3.toutiaoimg.com/origin/pgc-image/0c5b072b277649e89754cd09e8b644e3?from=pc

https://p3.toutiaoimg.com/origin/pgc-image/7f883ab8670d4cde8a02eb16456970bc?from=pc

原因也很简单,因为实际支撑调试运行的代码,并不是我们解压出来的那份 JDK 源码,那个仅仅是做关联用,实际运行用到的 JDK,还是之前系统安装好的那个 JDK 环境。

要想解决这个问题,那就只能使用自己修改过的代码来自行编译生成自己的 JDK,然后用到项目中去!

所以什么都憋说了,肝就完了!

首选说在前面的是,编译前的软件版本关系极其重要,自己在踩坑时,所出现的各种奇奇怪怪的问题几乎都和这个有关,后来版本匹配之后,就非常顺利了。

我们来盘点和梳理一下编译一个 JDK 需要哪些环境和工具:

1、boot JDK

我们要想编译 JDK,首先自己本机必须提前已经安装有一个 JDK,官方称之为 bootstrap JDK(或者称为 boot JDK)。

比如想编译 JDK 8,那本机必须最起码得有一个 JDK 7 或者更新一点的版本;你想编译 JDK 11,那就要求本机必须装有 JDK 10 或者 11。

所以鸡生蛋、蛋生鸡又来了…

2、Unix 环境

编译 JDK 需要 Unix 环境的支持!

这一点在 Linux 操作系统和 macOS 操作系统上已经天然的保证了,而对于 Windows 兄弟来说稍微麻烦一点,需要通过使用 Cygwin 或者 MinGW/MSYS 这种软件来模拟。

就像官方所说:在 Linux 平台编译 JDK 一般问题最少,容易成功;macOS 次之;Windows 上则需要稍微多花点精力,问题可能也多一些。

究其本质原因,还是因为 Windows 毕竟不是一个 Unix-Like 内核的系统,毕竟很多软件的原始编译都离不开 Unix Toolkit,所以相对肯定要麻烦一些。

3、编译器 / 编译工具链

JDK 底层源码(尤其 JVM 虚拟机部分)很多都是 C++/C 写的,所以相关编译器也跑不掉。

一图胜千言,各平台上的编译器支持如下表所示,按平台选择即可:

https://p3.toutiaoimg.com/origin/pgc-image/aca208c523724a469d4a5c3b2c3331fd?from=pc

4、其他工具

典型的比如:

  • Autoconf:软件源码包的自动配置工具
  • Make:编译构建工具
  • freetype:一个免费的渲染库,JDK 图形化部分的代码可能会用它

好,环境盘点就到这里,接下来具体列一下我在编译 JDK 8 和 JDK 11 时分别用到的软件详细版本信息:

编译 JDK 8 时:

  • 操作系统:macOS 10.11.6
  • boot JDK:JDK 1.8.0 (build 1.8.0_201-b09)
  • Xcode 版本:8.2
  • 编译器:Version 8.0.0 (at /usr/bin/clang)

编译 JDK 11 时:

  • 操作系统:macOS 10.15.4
  • boot JDK:JDK 11.0.7 (build 11.0.7+8-LTS)
  • Xcode 版本:11.5
  • 编译器:Version 11.0.3 (at /usr/bin/clang)

大家在编译时如果过程中有很多问题,大概率少软件没装,或者软件版本不匹配,不要轻易放弃,需要耐心自查一下。

下载 JDK 源码其实有两种方式。

方式一:通过 Mercurial 工具下载

Mercurial 可以理解为和 Git 一样,是另外一种代码管理工具,安装好之后就有一个 hg 命令可用。

https://p3.toutiaoimg.com/origin/pgc-image/25f54277286248268df07741b14052e2?from=pc

而 OpenJDK 的源码已经提前托管到 http://hg.openjdk.java.net/

因此,比如下载 JDK 8,可直接 hg clone 一下就行,和 git clone 一样:

1
hg clone http://hg.openjdk.java.net/jdk8/jdk8

同理,下载 JDK 11:

1
hg clone http://hg.openjdk.java.net/jdk/jdk11

但是这种方式下载速度不是很快。

方式二:直接下载打包好的源码包

下载地址:https://jdk.java.net/

https://p3.toutiaoimg.com/origin/pgc-image/6ed8112e61364fb88754e7d13a8406b5?from=pc

选择你想要的版本下载即可。

源码包下载好,放到本地某个目录(建议路径纯英文,避免不必要的麻烦),解压之,然后进入源码根目录,执行:

1
sh configure

当然这里运行的是默认配置项。

这一步会进行一系列的自动配置工作,时间一般很快,最终如果能出现一下提示,那么很幸运,编译前的配置工作就完成了!

这里我给出我自己分别在配置 JDK 11 和 JDK 8 时候完成时的样子:

配置 JDK 8 完成:

https://p3.toutiaoimg.com/origin/pgc-image/b00b426c27124296a1f0da5e74d8b8fe?from=pc

配置 JDK 11 完成:

https://p3.toutiaoimg.com/origin/pgc-image/92ec1b78dfae437681cba915c3135050?from=pc

注: 如果这一步出错,大概率是某个软件环境未装,或者即使装了,但版本不匹配,控制台打印日志里一般是会提醒的。

比如我在配置 JDK 8 的时候,就遇到了一个 errof:GCC compiler is required 的问题:

https://p3.toutiaoimg.com/origin/pgc-image/c41d93e6b9184227b6fb429c461a531f?from=pc

明明系统里已经有编译器,但还是报这个错误。通过后来修改 jdk 源码根目录 /common/autoconf/generated-configure.sh 文件,将相关的两行代码注释后就配置通过了

https://p3.toutiaoimg.com/origin/pgc-image/48bcb38d3bac413a9b6d4624abd7ca02?from=pc

https://p3.toutiaoimg.com/origin/pgc-image/0d077cb1c936407baa6ef8bcc50c5a55?from=pc

配置完成,接下来开始执行真正的编译动作了!

我们这里进行的是全量编译,直接在我们下载的 JDK 源码根目录下执行如下命令即可:

1
make all

这一步编译需要一点时间,耐心等待一下即可。编译过程如果有错误,会终止编译,如果能看到如下两个画面,那么则恭喜你,自己编译 JDK 源码就已经通过了,可以搞一杯咖啡庆祝一下了。

JDK 8 编译完成:

https://p3.toutiaoimg.com/origin/pgc-image/c3c89dfeeab1439fb95d96268b76c3a3?from=pc

JDK 11 编译完成:

https://p3.toutiaoimg.com/origin/pgc-image/c2cd6ee6c20a49498026cfece8f2fec9?from=pc

从两张图的对比可以看出,编译 JDK 8 和 JDK 11 完成时在输出上还是有区别的。时间上的区别很大程度上来源于 JDK 11 的编译机配置要高不少。

JDK 源码编译完成之后肯定会产生和输出很多产物,这也是我们所迫不及待想看到的。

由于 JDK 8 和 JDK 11 的源码包组织结构并不一样,所以输出东西的内容和位置也有区别。我们一一来盘点一下。

1、JDK 8 的编译输出

编译完成,build 目录下会生成一个 macosx-x86_64-normal-server-release 目录,所有的编译成果均位于其中。

首先,编译出来的 Java 可执行程序可以在如下目录里找到:

jdk 源码根目录 /build/macosx-x86_64-normal-server-release/jdk/bin

进入该目录后,可以输入./java -version 命令验证:

https://p3.toutiaoimg.com/origin/pgc-image/c2786d52d2eb421dac2c11a48c92c5ea?from=pc

其次,编译生成的成品 JDK 套装,可以在目录

1
jdk源码根目录/build/macosx-x86_64-normal-server-release/images

下找到,如图所示:

https://p3.toutiaoimg.com/origin/pgc-image/f1b3936d326c47bbb52849978ab4e967?from=pc

其中:

  • j2sdk-image:编译生成的 JDK
  • j2re-image:编译生成的 JRE

进入 j2sdk-image 目录会发现,里面的内容和我们平时从网络上下载的成品 JDK 内容一致。

https://p3.toutiaoimg.com/origin/pgc-image/aef82121c6c944719eb6aa356d6b2650?from=pc

2、JDK 11 的编译输出

JDK 11 的源码目录组织方式和 JDK 8 本身就有区别,编译生成的产物和上面编译 JDK 8 的输出有一定区别,但也不大。

JDK 11 编译完成,同样在 build 目录下会生成一个 macosx-x86_64-normal-server-release 目录,所有的编译成果均位于其中。

同样编译出来的 Java 可执行程序可以在目录

JDK 源码根目录 /build/macosx-x86_64-normal-server-release/jdk/bin

下看到,进入该目录后,也可以输入./java -version 命令验证:

https://p3.toutiaoimg.com/origin/pgc-image/18abf055b10c49fa999ce1c402ff5f62?from=pc

其次,编译生成的成品 JDK 11 套装,可以在目录

1
JDK源码根目录/build/macosx-x86_64-normal-server-release/images

下找到,如图所示:

https://p3.toutiaoimg.com/origin/pgc-image/e77975434d054d8eab61e7fbd36c4fef?from=pc

其中 jdk 目录就是编译生成的成品 JDK 11 套装。

既然我们已经动手编译出了 JDK 成品,接下来我们得用上哇。

新建一个最最基本的 Java 工程,比如命名为 JdkTest,目的是把我们自己编译出的 JDK 给用上。

https://p3.toutiaoimg.com/origin/pgc-image/6b4c221d16f34218a521e9ea0d05b5b3?from=pc

我们点开 Project Structure,选到 SDKs 选项,新添加上自己刚刚编译生成的 JDK,并选为项目的 JDK,看看是否能正常工作

https://p3.toutiaoimg.com/origin/pgc-image/de5d532a9845467e9499dfeb6619c836?from=pc

https://p3.toutiaoimg.com/origin/pgc-image/3fde6671465d444085f99d6cff9b5f70?from=pc

点击确定之后,我们运行之:

https://p3.toutiaoimg.com/origin/pgc-image/9df233d14b474aa59dc792b28ad7b46a?from=pc

可以看到我们自己编译出的 JDK 已经用上了。

我们继续在上一步 JdkTest 项目的 Project Structure → SDKs 里将 JDK 源码关联到自行下载的 JDK 源码路径上:

https://p3.toutiaoimg.com/origin/pgc-image/d5f617a8e1b54919874a4d323298bc38?from=pc

这样方便我们对自己下载的 JDK 源码进行阅读调试修改、以及在源码里随意做笔记加注释

举个最简单的例子,比如我们打开 System.out.println() 这个函数的底层源码:

https://p3.toutiaoimg.com/origin/pgc-image/e7754cf625a347d58ad9a15b639e44a2?from=pc

我们随便给它修改一下,加两行简单的标记,像这样:

https://p3.toutiaoimg.com/origin/pgc-image/75f5eb792ee2426eb65c37b099fed307?from=pc

为了使我们新加的代码行生效,我们必须要重新去 JDK 源码的根目录中再次执行 make images 重新编译生成 JDK 方可生效:

https://p3.toutiaoimg.com/origin/pgc-image/2420e015aa8345c985fb82d9907dc62c?from=pc

因为之前已经全量编译过了,所以再次 make 的时候增量编译一般很快。

重新编译之后,我们再次运行 JdkTest 项目,就可以看到改动的效果了:

https://p3.toutiaoimg.com/origin/pgc-image/cb9e8fa3390f44e8ab13de963eff294b?from=pc

记得之前搭建 《JDK 源码阅读环境》 时,大家可能发现了一个问题:阅读源码嘛,给源代码做点注释或笔记很常见!但那时候有个问题就是做注释时不可改变代码的行结构(只能行尾注释,不能跨行注释),否则 debug 调试时会出现行号错位的问题。

原因很简单,因为我们虽然做了源代码目录的映射,但是实际支撑运行的 JDK 还是预先安装好的那个 JDK 环境,并不是根据我们修改后的源码来重新编译构建的,所以看到这里,解决这个问题就很简单,就像上面一样自行编译一下 JDK 即可。

实际在实验时,还有一个很典型的问题是,当添加了多行的中文注释后,再编译居然会报错!

比如,还是以上面例子中最简单的 System.out.println() 源码为例,我们添加几行中文注释:

https://p3.toutiaoimg.com/origin/pgc-image/51f4dd90d6fd4793a5b5a125320f52a8?from=pc

这时候我们去 JDK 源码目录下编译会发现满屏类似这样的报错:

错误: 编码 ascii 的不可映射字符

https://p3.toutiaoimg.com/origin/pgc-image/1309cf5f322b4994904a8e28f6bef7cc?from=pc

顿时有点懵,毕竟仅仅是加了几行注释。对于我们来说,源码里写点多行的中文注释基本是刚需,然而编译竟会报错,这还能不能让人愉快的玩耍了… 当时后背有点发凉。

实不相瞒,就这个问题排查了一段时间,熬到了很晚。最终折腾了一番,通过如下这种方式解决了,顺便分享给小伙伴们,大家如果遇到了这个问题,可以参考着解决一下。

因为从控制台的报错可以很明显的看出,肯定是字符编码相关的问题导致的,而且都指向了 ascii 这种编码方式。

于是将 JDK 的源码从根目录导入了 Vs Code,然后全目录查找 encoding ascii 相关的内容,看看有没有什么端倪,结果发现

jdk 源码根目录 /make/common/SetupJavaCompilers.gmk 文件中有两处指定了 ascii 相关的编码方式:

https://p3.toutiaoimg.com/origin/pgc-image/b50bd66d8261415280aef22faa6a4c0f?from=pc

于是尝试将这两处 - encoding ascii 的均替换成 - encoding utf-8:

https://p3.toutiaoimg.com/origin/pgc-image/73a310813a81481fa9eed6a6d5a4a34e?from=pc

然后再次执行 make images 编译,编译顺利通过!

https://p3.toutiaoimg.com/origin/pgc-image/9aa47cd6be504c7eb31ae82c30577394?from=pc

至此大功告成!

这样后面不管是阅读调试还是定制 JDK 源码都非常方便了。

后记:这篇文章在开源项目:** https://github.com/hansonwang99/JavaCollection** 中也已经收录了,包含自学编程路线、面试题集合 / 面经、及系列技术文章等,资源持续更新中…

每天进步一点点

慢一点才能更快