HotSpot學習(一):編譯、啟動與除錯
前文
JVM對許多Java程式設計師是一個黑盒子,經常需要與它打交道,但是又搞不清內部的原理。
我出於以下幾個目的決定對JVM內部做一些學習:
- 之前對虛擬機器的瞭解停留在理論層面上,通過學習,做到知其然,知其所以然
- 工作中可能涉及JNI的一些除錯,JNI介面的C++端離不開JVM相關的結構和函式
- 在瞭解虛擬機器後,幫助改善程式效能
相關環境說明
以下是我的環境說明:
- 作業系統:Windows上通過VMStation裝了Ubuntu 18.04的虛擬機器
- 編譯的版本:直接從github上搜的openjdk的專案,從tag中找了openjdk8的原始碼包下載的
- 引導的JDK版本: 編譯JDK時需要用另一個JDK做引導(自舉?),我這裡用的
- 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命令關閉該訊號。