前文
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命令關閉該信號。