深入理解 JVM,应自己跟踪调试 JDK 到 JVM 源码开始,能更加容易上手 JVM,而学习的入门之路应从亲手实战编译一套 JDK。不论 JVM 底层还是 Java 高级开发都随时会跟 Linux 打交道,整个 JVM 学习应在 Linux 环境下展开,本人选择的编译环境是 Ubuntu14.04LTS(吐槽一下 Ubuntu16.04 更新推送,提示了几天还没更新上)。JDK 编译过程比较复杂繁琐,中间出现了各种莫名奇妙的编译 BUG 和依赖冲突等问题,在周志明《深入理解Java虚拟机》一书中描述得还不够详细,特写此指南供日后参考。
首先应明确 OpenJDK与其他JDK版本的关系。最广泛的 JDK 即 Oracle JDK,OpenJDK 在代码、性能等方面与 Oracle JDK 等非常接近。OpenJDK 一开始基于 JDK7 开发,即为 OpenJDK7,后来又衍生出 OpenJDK6 和 OpenJDK8等。我采用的是与书中一样的 OpenJDK7u。
从官网 Source Bundle Release 下载 OpenJDK 源码包并解压,注意,不应到root目录下,否则有时候会出现一些繁琐的权限问题。
由于 OpenJDK各个组成部分除了 C++ 实现外,还有很多代码需要由 Java 实现,故应该为编译构建 OpenJDK 代码提供一个 JDK,称之为 Bootstrap JDK。对应于 OpenJDK7,应使用 JDK6 Update 14 之后的版本,本人选择的是 jdk1.7.0_79,下载源码并解压。Ubuntu14.04的其他依赖可用以下命令完成
sudo apt-get install build essential gawk m4 openjdk-6-jdk libasound2-dev libcups2-dev libxrender-dev xorg-dev xutils-dev x11proto-print-dev binutils libmotif3 libmotif-dev ant
OpenJDK 和 Oracle JDK 之间存在版本兼容性问题,例如本人在编译时,会出现无法找到 libjvm.so 等问题,而把 Bootstrap JDK 改为 JDK6 时又好了。遇到的部分问题将在文后提出一些可能的解决方案。
进入到解压好的 OpenJDK 源码文件夹下创建一个编译 shell script,例如 build.sh
cd /home/jjfly/Software/openjdk
sudo vim build.sh
这里给出基于本人所写的 shell script 作参考:
# 语言选项,必须设置,否则编译好后会出现一个 HashTable 的 NPE错
export LANG=C
# Bootstrap JDK 解压路径,必须设置
export ALT_BOOTDIR=/home/jjfly/Software/jdk1.7.0_79
# 允许自动下载
export ALLOW_DOWNLOADS=true
# 并行编译线程数
export HOTSPOT_BUILD_JOBS=4
export ALT_PARALLEL_COMPILE_JOBS=4
# 比较本次 build 出来的映像与先前版本的差异,对我们没有意义
# 必须设置为 false,否则 sanity 检查为报缺少先前版本 JDK 的映像的错误提示
export SKIP_COMPARE_IMAGE=false
# 使用预编译头文件,不加这个编译会变慢
export USE_PRECOMPILED_HEADER=true
# 要编译的内容
export BUILD_LANGTOOLS=true
export BUILD_HOTSPOT=true
export BUILD_JDK=true
# export BUILD_JAXWS=false
# export BUILD_JAXP=false
# export BUILD_CORBA=false
# 要编译的版本
# export SKIP_DEBUG_BUILD=false
# export SKIP_FASTDEBUG_BUILD=true
# export DEBUG_NAME=debug
# 把它设置为 false 可以避开 javaws 和浏览器 Java 插件之类的部分的 build
BUILD_DEPLOY=false
# 把它设置为 false 就不会 build 出安装包,因为安装包里有奇怪的依赖
# 但即使不 build 出它也能得到完整的 JDK 映像,所以还是别 build
BUILD_INSTALL=false
# 编译结果所存放的路径
export ALT_OUTPUTDIR=/home/jjfly/Software/openjdk/build
# 这两个环境变量必须去掉,不然会发生奇怪的事情
# Makefile 检查到这两个变量就会提示警告
unset JAVA_HOME
unset CLASSPATH
make sanity
写脚本时有以下几点注意事项:
* 必须设置 LANG 和 ALT_BOOTDIR
* 必须 unset JAVA_HOME 和 CLASSPATH
* make sanity 可以检查前面设置的环境变量是否正确,若正确,此时会输出Sanity check passed
* 当检查正确后,将最后一行改为make sanity && make 2>&1 | tee $ALT_OUTPUTDIR/build.log
此时就会进入编译过程,当成功输出
#-- Build time ----------
Target all_product_build
...
----------------------------
说明OpenJDK已编译成功,编译过程中可能遇到的编译错误请参考文后。
java -version
cd /home/jjfly/Software/openjdk/build/j2sdk-image
java -version
LD_LIBRARY_PATH=.:${JAVA_HOME}/jre/lib/amd64/native_threads:${JAVA_HOME}/jre/lib/amd64:
export LD_LIBRARY_PATH
../env.sh
./gamma -version
编译过程中出现
cd linux_amd64_compiler2/product && ./test_gamma
java full version "1.7.0_79"
./gamma: relocation error: /usr/lib/jvm/java......libjava.so: symbol JVM_FindClassFormCaller, version SUMWprivate_1.1 not defined in file libjvm.so with link time reference
据说这是OpenJDK 和 Oracle JDK 版本冲突引起的 BUG。Google 和尝试后,总结了以下可能有效的解决方法,建议依次尝试:
1. 首先确认你的 Bootstrap JDK 依赖的是 Oracle JDK。
2. 将 JDK 版本改为 JDK6 最新版,或者将 OpenJDK 改为 OpenJDK8 的版本可能解决问题。
3. cd 到 openjdk/hotspot/make/linux ,将 Makefile 里 test_gamma 全部去掉。此方法亲测无效,会报 cd ./ 的 permission denial 的错误
4. StackOverflow 提供了一种解决方法,同样进入到上面的 Makefile,将 test_gamma所在行全部注释掉,尝试后确实有效。
使用到第四种方法解决问题的话,为第四节最后一步./gamma -version
无法运行埋下缘由,不过无碍。编译过程中若出现下述问题
Error: time is more than 10 years form present: .....
java.lang.RuntimeException: time is more than 10 years from present:
找到 CurrencyData.properties 文件,find -name 'CurrencyData.properties'
,将文本中所有的年份改为距现在10年内,重新启动脚本则无此问题。
《深入理解Java虚拟机》一书中关于NetBeans的调试是在中文环境下,而且 NetBeans 版本的差异导致部分设置产生了偏差,在这里详细叙述一下,避免入坑。
1. 到 NetBeans 网站上下载 NetBeans,选择支持 C/C++ 开发的版本。
2. 打开 NetBeans,左上角 new project,choose project 中选择 exsiting project,下一步。
3. choose mode 这一步中,以本人所在机子为例,将 folder 设置为 /home/jjfly/Software/openjdk/hotspot,下面的选项选择 custom,下一步。
4. 这一步不是中文版的构建工具,而是 prebuild action(好像是这个名字),没有 Makefile 配置选项,直接点下一步。
5. build action,分别将 folder 设置为 /home/jjfly/Software/openjdk/hotspot/make,
clean command 设置为${MAKE} -f Makefile clean
build command 设置为 ${MAKE} -f Makefile clean jvmg ALT_BOOTDIR=/home/jjfly/Softwre/jdk1.7.0_79 ARCH_DATA_MODEL=64 LANG=C
6. 直接下一步到最后,finish。此时 NetBeans 会进入一段 build 时间,持续约15-25分钟。
7. 此时程序没有运行入口。右键 project -> properties -> run,勾选build选项。
command 设置为 /home/jjfly/Software/openjdk/hotspot/build/linux/linux_amd64_compiler2/jvmg/gamma Queens
环境变量设置为如上述 env.sh 所设置的 JAVA_HOME,CLASSPATH 和 LD_LIBRARY_PATH。
8. 确定后,项目再次进入 build 过程,时间较短,约3-5分钟,之后即可run项目。
9. 为方便跟踪调试,可在 run command配置中添加以下参数
-XX:+TraceBytecodes -XX:StopInterpreterAt=
至此,OpenJDK 编译以及在 NetBeans 下的配置完毕。