好奇害死羊
很多小夥伴們做Java
開發,天天寫Java
程式碼,肯定離不開Java
基礎環境:JDK
,畢竟我們寫好的Java
程式碼也是跑在JVM
虛擬機器上。
一般來說,我們學Java
之前,第一步就是安裝JDK
環境。這個簡單啊,我們一般直接把JDK
從官網下載下來,安裝完成,配個環境變數就可以愉快地使用了。
不過話說回來,對於這個天天使用的東西,我們難道不好奇這玩意兒它到底是怎麼由原始碼編譯出來的嗎?
帶著這個原始的疑問,今天準備大幹一場,自己動動呆萌的小手,來編譯一個屬於自己的JDK
吧!
對了,本文在開源專案:https://github.com/hansonwang99/JavaCollection 中已收錄,包含自學程式設計路線、面試題集合/面經、及系列技術文章等,資源持續更新中...
還有個待填的坑
記得之前不是出過一期關於《JDK原始碼閱讀環境搭建》相關的視訊以及文章嘛,細心的小夥伴,可能會發現一個很實際的問題:
我們將src.zip
包裡的JDK
原始碼解壓出來,關聯到這份原始碼之後,除錯時是可以進,但是我們在加註釋的時候卻只能在行尾新增,並不能改變原始碼的行結構。換句話說,如果在原始碼中加了跨行的多行註釋,則debug
除錯的時候就會出現當前行的執行錯位問題,這個有點尷尬了。
當然那個視訊的評論區,的確也有幾個小夥伴提了這個問題:
原因也很簡單,因為實際支撐除錯執行的程式碼,並不是我們解壓出來的那份JDK
原始碼,那個僅僅是做關聯用,實際執行用到的JDK
,還是之前系統安裝好的那個JDK
環境。
要想解決這個問題,那就只能使用自己修改過的程式碼來自行編譯生成自己的JDK
,然後用到專案中去!
所以什麼都憋說了,肝就完了!
環境準備
首選說在前面的是,編譯前的軟體版本關係極其重要,自己在踩坑時,所出現的各種奇奇怪怪的問題幾乎都和這個有關,後來版本匹配之後,就非常順利了。
我們來盤點和梳理一下編譯一個JDK需要哪些環境和工具:
1、boot JDK
我們要想編譯JDK
,首先自己本機必須提前已經安裝有一個JDK
,官方稱之為bootstrap JDK
(或者稱為boot JDK
)。
比如想編譯JDK 8
,那本機必須最起碼得有一個JDK 7
或者更新一點的版本;你想編譯JDK 11
,那就要求本機必須裝有JDK 10
或者11
。
所以雞生蛋、蛋生雞又來了...
2、Unix環境
編譯JDK
需要Unix
環境的支援!
這一點在Linux
作業系統和macOS
作業系統上已經天然的保證了,而對於Windows
兄弟來說稍微麻煩一點,需要通過使用Cygwin
或者MinGW/MSYS
這種軟體來模擬。
就像官方所說:在Linux
平臺編譯JDK
一般問題最少,容易成功;macOS
次之;Windows
上則需要稍微多花點精力,問題可能也多一些。
究其本質原因,還是因為Windows
畢竟不是一個Unix-Like
核心的系統,畢竟很多軟體的原始編譯都離不開Unix Toolkit
,所以相對肯定要麻煩一些。
3、編譯器/編譯工具鏈
JDK
底層原始碼(尤其JVM
虛擬機器部分)很多都是C++/C
寫的,所以相關編譯器也跑不掉。
一圖勝千言,各平臺上的編譯器支援如下表所示,按平臺選擇即可:
4、其他工具
典型的比如:
-
Autoconf
:軟體原始碼包的自動配置工具 -
Make
:編譯構建工具 -
freetype
:一個免費的渲染庫,JDK
圖形化部分的程式碼可能會用它
好,環境盤點就到這裡,接下來具體列一下我在編譯JDK 8
和JDK 11
時分別用到的軟體詳細版本資訊:
編譯JDK 8時:
-
作業系統
:macOS 10.11.6 -
boot JDK
:JDK 1.8.0 (build 1.8.0_201-b09) -
Xcode版本
:8.2 -
編譯器
:Version 8.0.0 (at /usr/bin/clang)
編譯JDK 11時:
-
作業系統
:macOS 10.15.4 -
boot JDK
:JDK 11.0.7 (build 11.0.7+8-LTS) -
Xcode版本
:11.5 -
編譯器
:Version 11.0.3 (at /usr/bin/clang)
大家在編譯時如果過程中有很多問題,大概率少軟體沒裝,或者軟體版本不匹配,不要輕易放棄,需要耐心自查一下。
下載JDK原始碼
下載JDK
原始碼其實有兩種方式。
方式一:通過Mercurial工具下載
Mercurial
可以理解為和Git
一樣,是另外一種程式碼管理工具,安裝好之後就有一個hg
命令可用。
而OpenJDK
的原始碼已經提前託管到http://hg.openjdk.java.net/
。
因此,比如下載JDK 8
,可直接hg clone
一下就行,和git clone
一樣:
`hg clone http://hg.openjdk.java.net/jd...
`
同理,下載JDK 11
:
`hg clone http://hg.openjdk.java.net/jd...
`
但是這種方式下載速度不是很快。
方式二:直接下載打包好的原始碼包
下載地址:https://jdk.java.net/
選擇你想要的版本下載即可。
編譯前的自動配置
原始碼包下載好,放到本地某個目錄(建議路徑純英文,避免不必要的麻煩),解壓之,然後進入原始碼根目錄,執行:
`sh configure
`
當然這裡執行的是預設配置項。
這一步會進行一系列的自動配置工作,時間一般很快,最終如果能出現一下提示,那麼很幸運,編譯前的配置工作就完成了!
這裡我給出我自己分別在配置JDK 11
和JDK 8
時候完成時的樣子:
配置JDK 8完成:
配置JDK 11完成:
注: 如果這一步出錯,大概率是某個軟體環境未裝,或者即使裝了,但版本不匹配,控制檯列印日誌裡一般是會提醒的。
比如我在配置JDK 8
的時候,就遇到了一個errof:GCC compiler is required
的問題:
明明系統裡已經有編譯器,但還是報這個錯誤。通過後來修改 jdk原始碼根目錄/common/autoconf/generated-configure.sh
檔案,將相關的兩行程式碼註釋後就配置通過了
配置完成,接下來開始執行真正的編譯動作了!
真正的編譯動作
我們這裡進行的是全量編譯,直接在我們下載的JDK
原始碼根目錄下執行如下命令即可:
`make all
`
這一步編譯需要一點時間,耐心等待一下即可。編譯過程如果有錯誤,會終止編譯,如果能看到如下兩個畫面,那麼則恭喜你,自己編譯JDK
原始碼就已經通過了,可以搞一杯咖啡慶祝一下了。
JDK 8編譯完成:
JDK 11編譯完成:
從兩張圖的對比可以看出,編譯JDK 8
和JDK 11
完成時在輸出上還是有區別的。時間上的區別很大程度上來源於JDK 11
的編譯機配置要高不少。
驗證成果
JDK
原始碼編譯完成之後肯定會產生和輸出很多產物,這也是我們所迫不及待想看到的。
由於JDK 8
和JDK 11
的原始碼包組織結構並不一樣,所以輸出東西的內容和位置也有區別。我們一一來盤點一下。
1、JDK 8的編譯輸出
編譯完成,build
目錄下會生成一個macosx-x86_64-normal-server-release
目錄,所有的編譯成果均位於其中。
首先,編譯出來的Java
可執行程式可以在如下目錄裡找到:
jdk原始碼根目錄/build/macosx-x86_64-normal-server-release/jdk/bin
進入該目錄後,可以輸入./java -version
命令驗證:
其次,編譯生成的成品JDK
套裝,可以在目錄
`jdk原始碼根目錄/build/macosx-x86_64-normal-server-release/images
`
下找到,如圖所示:
其中:
-
j2sdk-image
:編譯生成的JDK -
j2re-image
:編譯生成的JRE
進入j2sdk-image
目錄會發現,裡面的內容和我們平時從網路上下載的成品JDK
內容一致。
2、JDK 11的編譯輸出
JDK 11的原始碼目錄組織方式和JDK 8本身就有區別,編譯生成的產物和上面編譯JDK 8的輸出有一定區別,但也不大。
JDK 11
編譯完成,同樣在build
目錄下會生成一個macosx-x86_64-normal-server-release
目錄,所有的編譯成果均位於其中。
同樣編譯出來的Java可執行程式可以在目錄
JDK原始碼根目錄/build/macosx-x86_64-normal-server-release/jdk/bin
下看到,進入該目錄後,也可以輸入./java -version
命令驗證:
其次,編譯生成的成品JDK 11
套裝,可以在目錄
`JDK原始碼根目錄/build/macosx-x86_64-normal-server-release/images
`
下找到,如圖所示:
其中jdk
目錄就是編譯生成的成品JDK 11
套裝。
使用自己編譯的JDK
既然我們已經動手編譯出了JDK
成品,接下來我們得用上哇。
新建一個最最基本的Java
工程,比如命名為JdkTest
,目的是把我們自己編譯出的JDK
給用上。
我們點開Project Structure
,選到SDKs
選項,新新增上自己剛剛編譯生成的JDK,並選為專案的JDK,看看是否能正常工作
點選確定之後,我們執行之:
可以看到我們自己編譯出的JDK已經用上了。
關聯JDK原始碼並修改
我們繼續在上一步JdkTest
專案的Project Structure
→ SDKs
裡將JDK
原始碼關聯到自行下載的JDK原始碼路徑上:
這樣方便我們對自己下載的JDK原始碼
進行閱讀、除錯、修改、以及在原始碼裡隨意做筆記和加註釋。
舉個最簡單的例子,比如我們開啟System.out.println()
這個函式的底層原始碼:
我們隨便給它修改一下,加兩行簡單的標記,像這樣:
為了使我們新加的程式碼行生效,我們必須要重新去JDK原始碼的根目錄中再次執行 make images
重新編譯生成JDK方可生效:
因為之前已經全量編譯過了,所以再次make
的時候增量編譯一般很快。
重新編譯之後,我們再次執行JdkTest
專案,就可以看到改動的效果了:
多行註釋的問題
記得之前搭建《JDK原始碼閱讀環境》時,大家可能發現了一個問題:閱讀原始碼嘛,給原始碼做點註釋或筆記很常見!但那時候有個問題就是做註釋時不可改變程式碼的行結構(只能行尾註釋,不能跨行註釋),否則debug除錯時會出現行號錯位的問題。
原因很簡單,因為我們雖然做了原始碼目錄的對映,但是實際支撐執行的JDK
還是預先安裝好的那個JDK環境,並不是根據我們修改後的原始碼來重新編譯構建的,所以看到這裡,解決這個問題就很簡單,就像上面一樣自行編譯一下JDK
即可。
實際在實驗時,還有一個很典型的問題是,當新增了多行的中文註釋後,再編譯居然會報錯!
比如,還是以上面例子中最簡單的System.out.println()
原始碼為例,我們新增幾行中文註釋:
這時候我們去JDK原始碼目錄下編譯會發現滿屏類似這樣的報錯:
錯誤: 編碼 ascii 的不可對映字元
頓時有點懵,畢竟僅僅是加了幾行註釋。對於我們來說,原始碼裡寫點多行的中文註釋基本是剛需,然而編譯竟會報錯,這還能不能讓人愉快的玩耍了... 當時後背有點發涼。
實不相瞞,就這個問題排查了一段時間,熬到了很晚。最終折騰了一番,通過如下這種方式解決了,順便分享給小夥伴們,大家如果遇到了這個問題,可以參考著解決一下。
因為從控制檯的報錯可以很明顯的看出,肯定是字元編碼相關的問題導致的,而且都指向了ascii
這種編碼方式。
於是將JDK的原始碼從根目錄匯入了Vs Code,然後全目錄查詢encoding ascii
相關的內容,看看有沒有什麼端倪,結果發現
jdk原始碼根目錄/make/common/SetupJavaCompilers.gmk
檔案中有兩處指定了ascii
相關的編碼方式:
於是嘗試將這兩處-encoding ascii
的均替換成-encoding utf-8
:
然後再次執行make images
編譯,編譯順利通過!
至此大功告成!
這樣後面不管是閱讀、除錯還是定製JDK
原始碼都非常方便了。
後記:這篇文章在開源專案:https://github.com/hansonwang99/JavaCollection 中也已經收錄了,包含自學程式設計路線、面試題集合/面經、及系列技術文章等,資源持續更新中...
每天進步一點點
慢一點才能更快