1. 程式人生 > 實用技巧 >HotSpot學習(一):編譯、啟動與除錯

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的,之後的版本介面可能有些許不同,但也大差不差。

第一步:匯入專案

第二步:選中openjdk的根目錄

第三步:選擇Import as a new CMake project

第四步:在彈出的對話方塊中,一定要把編譯結果中的jdk/bin路徑選上

專案匯入後,CLion會現對整個專案進行一次索引,過程可能會持續幾分鐘,待索引結束後,我們便可以開始配置除錯的環境。

第五步:配置除錯環境

完成以上步驟後,就可以執行程式開始除錯。

除錯中遇到的問題

我在除錯過程中還是比較順利的,唯一遇到的問題就是斷電無法進入jni.cpp中,如果強制進入的話 會跳入彙編的介面。
這個可以將找一下libjvm.diz中的libjvm.debuginfo解壓出來(注意:壓縮包的位置需要找對)

另一個問題是在除錯時,可能會有SIGSEGV訊號出現,這個不會影響除錯過程,如果你不喜歡,可以用GDB的handle nostop命令關閉該訊號。