1. 程式人生 > >Ubuntu 18.04.1下原始碼編譯安裝OpenJDK8

Ubuntu 18.04.1下原始碼編譯安裝OpenJDK8

自己編譯個JDK來提升對JVM的興趣。本文分三部分來描述編譯OpenJDK的過程,分別是編譯前準備工作、構建編譯環境、進行編譯,在這三部分內容中順帶把趟的坑一起說明下。
 
一、編譯前準備工作

1.1 安裝Linux環境

https://askubuntu.com/questions/1070593/lenovo-thinkpad-e480-no-wifi-adaptor-found-in-ubuntu-18-04

1.2 下載OpenJDK原始碼

  原本是計劃按照周老師的書一步一步的操作,所以計劃的是編譯OpenJDK7,OpenJDK7對應的BootStrapJDK是OpenJDK6,無奈OpenJDK6在Ubuntu 18.04.1 LTS上很難再找到資源,故放棄了這個思路,改成編譯OpenJDK8,BootStrapJDK是OpenJDK7,實踐證明這個操作也是一路的坑,後面環節再敘述。

  確定了思路後,接下來就是下載OpenJDK8的原始碼,有兩種方式:

  第一種就是Mercurial,優點就是操作起來很簡單,不需要再解壓檔案包,缺點就是需要耗費的時間長一些,實際上本人最終就是使用的這種方式,預計耗時半小時左右。Mercurial也是一種版本管理工具,大家可以想象下SVN、Git之類的工具。下載程式碼的命令如下:

hg clone http://hg.openjdk.java.net/jdk8u/jdk8u-dev
cd jdk8u-dev
sh get_source.sh

第二種就是手動方式,說白了就是自己去下載原始碼包,然後解壓,優點就是耗時短,但相對來說如果不會查詢資源,就只能下載到老版本的原始碼,比如我就只找到這個連結http://jdk.java.net/java-se-ri/8下的原始碼,這個版本是2015年的版本,距離現在已經過去了三年,這樣的程式碼其實在後面的編譯過程中如果遇到一些問題就無法判斷是Linux的問題還是OpenJDK8的程式碼問題。
 
  到目前為止,已經有了Linux作業系統,需要編譯的OpenJDK原始碼也已經有了,下一個環節便是思考如何構建編譯環境。

二、構建編譯環境

  學習了這麼多年,大家應該都具備了一定的學習方法。做IT的一個很重要的學習方法就是在拿到資料後,最好先翻閱下這個資料的DEMO或者是README之類的。同樣,OpenJDK原始碼目錄下也有這樣一個檔案,叫做README-builds.html。

  這個檔案基本上貫穿了咱們本文的操作流程,首先來看下Introduction:

  • The build is now a "configure && make" style build
  • Any GNU make 3.81 or newer should work
  • The build should scale, i.e. more processors should cause the build to be done in less wall-clock time
  • Nested or recursive make invocations have been significantly reduced, as has the total fork/exec or spawning of sub processes during the build
  • Windows MKS usage is no longer supported
  • Windows Visual Studio vsvars*.bat and vcvars*.bat files are run automatically
  • Ant is no longer used when building the OpenJDK
  • Use of ALT_* environment variables for configuring the build is no longer supported

  和OpenJDK7的構建相比,已經不再需要Ant,另外ALT_* 的環境變數也不再支援,OpenJDK7的編譯過程可檢視周老師的書,也可以網上查閱其他資料。

  檔案的第二部分內容是下載原始碼,目前程式碼下載環節已在本文1.2中體現,這裡不再贅述。

  第三部分就是Building,這裡聲明瞭各個作業系統環境中的軟體硬體要求,明確要求了OpenJDK8的boot JDK是JDK 7。

2.1 安裝boot JDK

  在檔案中的Specific Developer Build Environments部分實際也約定了如何安裝boot JDK,命令如下(在Ubuntu 18.04.1 LTS中aptitude 應該改成apt-get):

sudo aptitude build-dep openjdk-7
sudo aptitude install openjdk-7-jdk

實際執行下來,如上命令也是不成功的,提示沒有可安裝候選,這個也就是本文1.2提到的其中一個坑,該如何解決呢?請看https://askubuntu.com/questions/761127/how-do-i-install-openjdk-7-on-ubuntu-16-04-or-higher,裡面的ppa方式也已經過期了,只能按MDMower描述的方案來操作,我這邊選擇了Manual Installation,最終成功安裝boot JDK,結果如下:

2.2 依賴檢查

  實際上如果是按照README-builds.html的流程,在安裝boot JDK之前是先進行依賴檢查的,即使沒有先安裝boot JDK,直接通過bash ./configure來檢查的話,這步最先提示的也是安裝boot JDK,提示如下:
 
configure: Could not find a valid Boot JDK. You might be able to fix this by running 'sudo apt-get install openjdk-7-jdk'.
configure: This might be fixed by explicitely setting –with-boot-jdk

  在完成本文2.1後,接下來就是遞迴執行bash ./configure來檢查編譯環境的依賴項是否全部安裝完成。直到看到這個結果:

Configuration summary:
* Debug level:    release
* JDK variant:    normal
* JVM variants:  server
* OpenJDK target: OS: linux, CPU architecture: x86, address length: 64

Tools summary:
* Boot JDK:      java version "1.7.0_161" OpenJDK Runtime Environment (IcedTea 2.6.12) (7u161-2.6.12-1) OpenJDK 64-Bit Server VM (build 24.161-b01, mixed mode)  (at /usr/lib/jvm/java-7-openjdk-amd64)
* Toolchain:      gcc (GNU Compiler Collection)
* C Compiler:    Version 7.3.0 (at /usr/bin/gcc)
* C++ Compiler:  Version 7.3.0 (at /usr/bin/g++)

Build performance summary:
* Cores to use:  7
* Memory limit:  7872 MB

  這裡再補充說明下,在遞迴執行依賴檢查的過程中可能會提示這個 libx11-dev,Ubuntu 18.04.1 LTS是這麼提示安裝專案的:
 
sudo apt-get install libX11-dev libxext-dev libxrender-dev libxtst-dev libxt-dev

  libX11-dev的X是大寫的,應該會提示找不到這個依賴項,這個時候要把大寫X改成小寫的x,為 libx11-dev,就可以找到依賴項了。
  到這一步,OpenJDK8的編譯環境就已經準備好了,下一步就是編譯OpenJDK8。

三、進行編譯

  編譯的程式碼很簡單,直接make all即可,當然也可以按照README-builds.html中對make執行帶引數編譯,說明如下:
 

Make Target

Description

empty

build everything but no images

all

build everything including images

all-conf

build all configurations

images

create complete j2sdk and j2re images

install

install the generated images locally, typically in /usr/local

clean

remove all files generated by make, but not those generated by configure

dist-clean

remove all files generated by both and configure (basically killing the configuration)

help

give some help on using make, including some interesting make targets

  在編譯前還有幾個注意事項,這些注意事項在檔案README-builds.html中也是有體現的:

  設定語言選項,可先執行echo $LANG,看下輸出,如果不是C,則執行export LANG=C;

  設定PATH,可先執行echo $PATH,看下輸出,如果沒有boot JDK,則執行export PATH="/usr/lib/jvm/java-7-openjdk-amd64/bin:${PATH}";

  檢查JAVA_HOME ,可先執行echo $JAVA_HOME,看下輸出,如果有值則需要unset JAVA_HOME;

  這三步檢查執行通過後,就可以執行make命令了。一切順利的話,就可以看到這樣的編譯結果:

## Finished docs (build time 00:01:46)

----- Build times -------
Start 2018-09-23 16:59:30
End  2018-09-23 17:08:39
00:00:19 corba
00:00:13 demos
00:01:46 docs
00:03:26 hotspot
00:00:18 images
00:00:13 jaxp
00:00:17 jaxws
00:02:01 jdk
00:00:25 langtools
00:00:11 nashorn
00:09:09 TOTAL
-------------------------
Finished building OpenJDK for target 'all'

看到這樣的結果,表示編譯成功,可以到多個目錄下的bin目錄執行./java -version來驗證。

  事實上,我在編譯的過程中就不順利,主要遇到了兩個問題:
 
1、編譯核心版本問題
 
   在本文1.2中已經提到了兩種獲取原始碼的方式,其實一開始我採用的是方法二,下載的是2015年的openjdk-8u40,這個原始碼包中的/hotspot/make/linux/Makefile檔案中宣告的SUPPORTED_OS_VERSION不支援4.X的核心,所以編譯報如下截圖的錯誤:

  因為Ubuntu 18.04.1 LTS的核心是4.15.0-34-generic,故如果要繼續編譯下去,需要將Makefile的SUPPORTED_OS_VERSION那行後面新增4%。

2、-Werror=deprecated-declarations問題

  在我把問題1解決後,繼續編譯,後面又碰到了很多神奇的問題,而且很難查詢到相關解決問題的資料。所以我只能從邏輯上推理下,OpenJDK8一直在更新發展,Ubuntu 也一直在更新發展,兩者同步更新,應該取最新的檔案編譯起來問題才會少一些,而且猜測也有更多的資料可查,但是現在用的是2015年的openjdk-8u40,而 Ubuntu又是最新的,所以有問題估計也沒有人去修復(其實我們的很多應用系統一樣也是這個道理,年久失修,沒什麼人用的功能有問題也不一定去修復)。這個時候我果斷切換到最新的OpenJDK8,通過Mercurial下載最新的程式碼,然後在Ubuntu 18.04.1 LTS編譯。編譯的過程就碰到一個問題,報錯如下:
 
os_linux.inline.hpp:127:18: error: 'int readdir_r(DIR*, dirent*, dirent**)' is deprecated [-Werror=deprecated-declarations]

  查閱網上資料說是這是因為glibc >= 2.24的情況下,方法 readdir_r被 deprecated,不支援了,通過getconf GNU_LIBC_VERSION檢查發現Ubuntu 18.04.1 LTS版本為glibc 2.27,而且也有很多人在OpenJDK上報了BUG,連結https://bugs.openjdk.java.net/browse/JDK-8179887,6/7/8/9都不打算修復此問題,會在11修復這個BUG,所以當前只能通過其他的方式來解決,解決方案如下:

在./hotspot/make/linux/makefiles/gcc.make檔案中找到WARNINGS_ARE_ERRORS = -Werro,註釋該段或改成WARNINGS_ARE_ERRORS = -Wno-all。再編譯就會忽略掉警告,直到編譯完成。