HotSpot学习(一):编译、启动与调试

前文

JVM对许多Java程序员是一个黑盒子,经常需要与它打交道,但是又搞不清内部的原理。
我出于以下几个目的决定对JVM内部做一些学习:

  • 之前对虚拟机的了解停留在理论层面上,通过学习,做到知其然,知其所以然
  • 工作中可能涉及JNI的一些调试,JNI接口的C++端离不开JVM相关的结构和函数
  • 在了解虚拟机后,帮助改善程序性能

相关环境说明

以下是我的环境说明:

  • 操作系统:Windows上通过VMStation装了Ubuntu 18.04的虚拟机
  • 编译的版本:直接从github上搜的openjdk的项目,从tag中找了openjdk8的源码包下载的
  • 引导的JDK版本: 编译JDK时需要用另一个JDK做引导(自举?),我这里用的jdk.1.7.0_76
  • gcc的版本:编译时会由于gcc的版本的过高报错,我重新换了gcc v4.8.5

基本的编译环境就是以上这些。

编译过程

我之前参考的是《深入理解Java虚拟机》和《HotSpot实战》两本书进行编译的。走了不少弯路,主要原因是书中介绍的编译方式相对复杂,而且是针对JDK7甚至更早之前的版本。 现在已经不具有太多的参考价值。
openjdk8开始采用了相对简单的编译方式:

 configure & make

也就是编译方式共分为两步:配置和编译。

配置

配置主要是通过configure命令生成Makefile。在openjdk的主目录下输入以下命令:

bash ./configure --with-target-bits=64 --with-debug-level=slowdebug --with-boot-jdk=/your boot jdk path/jdk1.7.0_80/

以上几个参数分本指定了编译的版本、编译的等级和引导JDK的路径(需要改完你自己的boot jdk所在的路径)

编译

配置完后,就可以用make命令开始编译了:

make all DEBUG_BINARIES=true

我直接选了make all,会对所有部分都进行编译。

之后大概等十多分钟,如果看到输出类似的内容说明编译成功(这里的时间较短,是因为我之前已经编译过):

---- Build times -------
Start 2020-12-19 11:27:05
End   2020-12-19 11:28:33
00:00:00 corba
00:00:00 demos
00:01:15 docs
00:00:00 hotspot
00:00:08 images
00:00:00 jaxp
00:00:00 jaxws
00:00:03 jdk
00:00:01 langtools
00:00:00 nashorn
00:01:28 TOTAL
-------------------------
Finished building OpenJDK for target 'all'

编译完成后,我们可以看到之前存JDK源码的地方多了build的文件夹,下面有一个linux-x86_64-normal-server-slowdebug的文件夹,这就是我们编译生成的文件,其中的内容如下:

xieshang@xieshang-virtual-machine:~/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug$ ls
bootcycle-spec.gmk  config.status        hotspot-spec.gmk  Makefile
build.log           configure-arguments  images            nashorn
build.log.old       corba                jaxp              source_tips
compare.sh          docs                 jaxws             spec.gmk
config.h            docstemp             jdk               spec.sh
config.log          hotspot              langtools         tmp

验证编译是否正确的简单方法可以执行java -version命令,看程序是否可以正常输出。
进入上面路径下的./jdk/bin目录下,然后运行./java -version,观察输出,如果正常输版本,则说明编译正确。

xieshang@xieshang-virtual-machine:~/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/bin$ ./java -version
openjdk version "1.8.0-internal-debug"
OpenJDK Runtime Environment (build 1.8.0-internal-debug-xieshang_2020_12_18_09_49-b00)
OpenJDK 64-Bit Server VM (build 25.40-b25-debug, mixed mode)

我在编译过程中碰到的问题

缺少依赖

编译过程中如果缺少依赖,可以根据提示,通过apt-get install命令安装

gcc版本过高

这个在上文中提到过了,可以在装一个低版本的gcc。可以参考这里

系统版本不对

如果出现类似如下的错误

recipe for target 'check_os_version' failed

说明是操作系统内核版本过高,编译时校验不通过,解决的办法是修改./hotspot/make/linux/Makefile, 文件中搜索SUPPORTED_OS_VERSION

修改228行内容: SUPPORTED_OS_VERSION = 2.4% 2.5% 2.6% 2.7% 为 
SUPPORTED_OS_VERSION = 2.4% 2.5% 2.6% 2.7% 3% 4%

说一个罕见的坑

由于我在系统环境变量中打开了_Java_LAUNCHER_DEBUG标志,导致在gensrc时,生成Java代码中包含了很多debug的打印,破坏了Java类的结构,导致编译失败。
这种情况通常也不会出现,如果出现只需要在环境变量中删除这个标志即可。
报错的内容如下:

Generating Nimbus source files
[Error] encoded value was less than 0: encode(-8.326673E-17, 5.0, 11.0, 16.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] encoded value was greater than 3: encode(15.029411, 1.0, 14.0, 15.0)
[Error] encoded value was less than 0: encode(-0.05882353, 1.0, 24.0, 25.0)
[Error] Encountered Infinity: encode(-0.00877193, 0.0, 7.0, 7.0)
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1583: 错误: 非法的类型开始
----_JAVA_LAUNCHER_DEBUG----
^
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1583: 错误: 需要';'
----_JAVA_LAUNCHER_DEBUG----
  ^
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1583: 错误: 需要<标识符>
----_JAVA_LAUNCHER_DEBUG----
                        ^
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1583: 错误: 非法的类型开始
----_JAVA_LAUNCHER_DEBUG----
                          ^
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1584: 错误: 需要';'
Launcher state:
        ^
/home/xieshang/learn-jvm/openjdk/build/linux-x86_64-normal-server-slowdebug/jdk/gensrc/java/nio/ByteBuffer.java:1584: 错误: 需要<标识符>
Launcher state:[1217/142857.833:ERROR:directory_reader_win.cc(43)] 
...后续省略

调试

调试的过程中,我也踩了不少坑。
选择的IDE从最初的NetBeans到VSCode再到CLion,可以说都试了个遍。
除了NetBeans没成功,后两个都能正常调试了。
Java程序员可能对IDEA比较熟悉,因此我在这里介绍使用CLion的调试步骤。
我用的版本是2020.1的,之后的版本界面可能有些许不同,但也大差不差。

第一步:导入项目
HotSpot学习(一):编译、启动与调试

第二步:选中openjdk的根目录
HotSpot学习(一):编译、启动与调试

第三步:选择Import as a new CMake project
HotSpot学习(一):编译、启动与调试

第四步:在弹出的对话框中,一定要把编译结果中的jdk/bin路径选上
HotSpot学习(一):编译、启动与调试

项目导入后,CLion会现对整个项目进行一次索引,过程可能会持续几分钟,待索引结束后,我们便可以开始配置调试的环境。

第五步:配置调试环境
HotSpot学习(一):编译、启动与调试

完成以上步骤后,就可以运行程序开始调试。
HotSpot学习(一):编译、启动与调试

调试中遇到的问题

我在调试过程中还是比较顺利的,唯一遇到的问题就是断电无法进入jni.cpp中,如果强制进入的话 会跳入汇编的界面。
这个可以将找一下libjvm.diz中的libjvm.debuginfo解压出来(注意:压缩包的位置需要找对)
HotSpot学习(一):编译、启动与调试

另一个问题是在调试时,可能会有SIGSEGV信号出现,这个不会影响调试过程,如果你不喜欢,可以用GDB的handle nostop命令关闭该信号。
HotSpot学习(一):编译、启动与调试

上一篇:20201230-3 bytes数据类型


下一篇:iOS Plist 文件的 增 删 改