close

當我們執行 java -version 命令時,通常會看到如下信息。

java version "1.8.0_201"Java(TM) SE Runtime Environment (build 1.8.0_201-b09)Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode

當然,這是 oracle jdk 8u201 版本的輸出結果。

如果我們是用的是 openJDK 構建出的 jdk 來看,它會是這樣的。

openjdk version "1.8.0-internal"OpenJDK Runtime Environment (build 1.8.0-internal-root_2022_06_08_12_25-b00)OpenJDK 64-Bit Server VM (build 25.71-b00, mixed mode

當然,這是在沒有修改源碼且沒有增加多餘 configure 參數構建出的結果。

今天我想改一改這個輸出信息,讓它成為我自己擁有的 jdk,應該怎麼辦呢?

首先,我們下載 openjdk 8u312 版本的源碼,可以從 openjdk 官網下載,也可以從一個更開發者友好的 adoptopenjdk 網站下載。

https://github.com/AdoptOpenJDK/openjdk8-upstream-binaries

下載好源碼後,我們一頓操作猛如虎,配置、編譯、打斷點、運行調試,進入了 java -version 命令所執行到的關鍵源碼位置。

可以看出,它最終執行了 sun/misc/Version 類的 print 方法,我們打開這個類。

publicclassVersion{privatestaticfinalStringlauncher_name="openjdk";privatestaticfinalStringjava_version="1.8.0-internal-debug";privatestaticfinalStringjava_runtime_name="OpenJDKRuntimeEnvironment";privatestaticfinalStringjava_profile_name="";privatestaticfinalStringjava_runtime_version="1.8.0-internal-debug-root_2022_06_09_16_41-b00";publicstaticvoidprint(){print(System.err);}publicstaticvoidprint(PrintStreamps){.../*Firstline:platformversion.*/ps.println(launcher_name+"version\""+java_version+"\"");.../*Secondline:runtimeversion(ie,libraries).*/ps.print(java_runtime_name+"(build"+java_runtime_version);.../*Thirdline:JVMinformation.*/Stringjava_vm_name=System.getProperty("java.vm.name");Stringjava_vm_version=System.getProperty("java.vm.version");Stringjava_vm_info=System.getProperty("java.vm.info");ps.println(java_vm_name+"(build"+java_vm_version+","+java_vm_info+")");}

這是段 Java 代碼,所以非常好理解,print 函數裡面就是簡單粗暴地輸出了三行文字。

而這些輸出信息,是通過一個個變量拼接的,變量的定義就在上方,比如 launcher_name,java_version 等等。

那看來我們只需要修改這個文件的 print 函數,或者修改上面定義的變量的值,就可以做到修改輸出信息了。

但是呢,這個 Version 文件並不在 openJDK 源碼里,而在 openJDK 源碼構建出的產物 build 文件夾里。

也就是說,這個 Version 文件是構建過程中生成的,想要修改這個 Version 文件,就得找到這個 Version 文件是怎麼構造出來的。

實際上,Version 文件是由 Version.java.template 文件生成的,這個文件是在源碼里。

//jdk/src/share/classes/sun/misc/Version.java.templatepublicclassVersion{privatestaticfinalStringlauncher_name="@@launcher_name@@";privatestaticfinalStringjava_version="@@java_version@@";privatestaticfinalStringjava_runtime_name="@@java_runtime_name@@";privatestaticfinalStringjava_profile_name="@@java_profile_name@@";privatestaticfinalStringjava_runtime_version="@@java_runtime_version@@";publicstaticvoidprint(PrintStreamps){.../*Firstline:platformversion.*/ps.println(launcher_name+"version\""+java_version+"\"");.../*Secondline:runtimeversion(ie,libraries).*/ps.print(java_runtime_name+"(build"+java_runtime_version);.../*Thirdline:JVMinformation.*/Stringjava_vm_name=System.getProperty("java.vm.name");Stringjava_vm_version=System.getProperty("java.vm.version");Stringjava_vm_info=System.getProperty("java.vm.info");ps.println(java_vm_name+"(build"+java_vm_version+","+java_vm_info+")");}

可以看出,這個文件是個模板文件,與剛剛生成的 Version 文件只差了上面那些常量的值,是通過 @@XXX@@ 這種占位符替換做到的。

那我們接下來就應該尋找,這些占位符變量的值,分別是什麼,就知道應該如何修改它了。

探索後發現,Version.java.template 這個文件里的 @@XXX@@ 占位符,會由 GensrcMisc.gmk 文件進行替換。

//jdk/make/gensrc/GensrcMisc.gmk#################################################################################Installthelaunchername,releaseversionstring,fullversion#stringandtheruntimenameintotheVersion.javafile.#Tobeprintedbyjava-version$(JDK_OUTPUTDIR)/gensrc/sun/misc/Version.java\$(PROFILE_VERSION_JAVA_TARGETS):\$(JDK_TOPDIR)/src/share/classes/sun/misc/Version.java.template$(MKDIR)-p$(@D)$(RM)$@$@.tmp$(ECHO)Generatingsun/misc/Version.java$(callprofile_version_name,$@)$(SED)-e's/@@launcher_name@@/$(LAUNCHER_NAME)/g'\-e's/@@java_version@@/$(RELEASE)/g'\-e's/@@java_runtime_version@@/$(FULL_VERSION)/g'\-e's/@@java_runtime_name@@/$(RUNTIME_NAME)/g'\-e's/@@java_profile_name@@/$(callprofile_version_name,$@)/g'\$<>$@.tmp$(MV)$@.tmp$@

可以看出,這裡用了 shell 腳本中的 sed 命令做占位符的替換,比如,將 @@java_version@@ 的值替換為 $(RELEASE)。

很可惜,$(RELEASE) 也是個 shell 腳本的變量,我們還得尋找這部分變量的來源。

再經過一番探索,我們發現,這些變量會在 spec.gmk.in 以及 version-numbers 中定義,比如

//common/autoconf/spec.gmk.inJDK_VERSION:=@JDK_VERSION@BUILD_VARIANT_RELEASE:=@BUILD_VARIANT_RELEASE@MILESTONE:=@MILESTONE@ifeq($(MILESTONE),fcs)RELEASE=$(JDK_VERSION)$(BUILD_VARIANT_RELEASE)elseRELEASE=$(JDK_VERSION)-$(MILESTONE)$(BUILD_VARIANT_RELEASE)endififneq($(USER_RELEASE_SUFFIX),)FULL_VERSION=$(RELEASE)-$(USER_RELEASE_SUFFIX)-$(JDK_BUILD_NUMBER)elseFULL_VERSION=$(RELEASE)-$(JDK_BUILD_NUMBER)endifJRE_RELEASE_VERSION:=$(FULL_VERSION

很是可惜,這裡面仍然是變量替換,我們繼續往下尋找。

拿 JDK_VERSION 這個變量來說,找到它在 generated-configure.sh 中有這樣的賦值方式。

//common/autoconf/generated-configure.sh...#Checkwhether--with-update-versionwasgiven.JDK_UPDATE_VERSION="$with_update_version"...iftest"x$JDK_UPDATE_VERSION"!=x;thenJDK_VERSION="${JDK_MAJOR_VERSION}.${JDK_MINOR_VERSION}.${JDK_MICRO_VERSION}_${JDK_UPDATE_VERSION}"elseJDK_VERSION="${JDK_MAJOR_VERSION}.${JDK_MINOR_VERSION}.${JDK_MICRO_VERSION}"f

這裡的 --with-update-version 就是我們在編譯 openJDK 時,./configure 傳入的參數。

好了,我們現在終於找到根了!

也就是說,假如我們在 configure 的時候傳入了

--with-update-version=XXX

這樣的參數:

那麼 JDK_UPDATE_VERSION 將會等於 XXX

進而導致 JDK_VERSION = 1.8.0_XXX

進而導致 RELEASE = 1.8.0_XXX-internal

進而導致 @@java_version@@ = 1.8.0_XXX-internal

進而導致 java -version 打印出的第一行字符串為

openjdk version "1.8.0_XXX-internal"

而不再是

openjdk version "1.8.0-internal"

其他內容同理可知,我們直接說結論。

我們重新編譯 openJDK,加入編譯參數。

./configure\--with-milestone=fcs\--with-update-version=312\--with-build-number=b00makeal

在得出的產物中,執行 java -version,將會得到如下信息。

openjdkversion"1.8.0_312"OpenJDKRuntimeEnvironment(build1.8.0_312-b07)OpenJDK64-BitServerVM(build25.71-b00,mixedmode

這和一開始的默認版本,就不一樣了!

openjdkversion"1.8.0-internal"OpenJDKRuntimeEnvironment(build1.8.0-internal-root_2022_06_08_12_25-b00)OpenJDK64-BitServerVM(build25.71-b00,mixedmode

OK!我們這就成功得到了一個我們自己定製的 openJDK 發行版!

不過先別高興得太早,我們來看幾個業界知名的企業 JDK 發行版的 java -verison 信息。

騰訊 Kona 的是這樣的。

openjdkversion"1.8.0_322"OpenJDKRuntimeEnvironment(TencentKona8.0.9)(build1.8.0_322-b1)OpenJDK64-BitServerVM(TencentKona8.0.9)(build25.322-b1,mixedmode,sharing

阿里 Dragonwell 的是這樣的。

openjdkversion"1.8.0_322"OpenJDKRuntimeEnvironment(AlibabaDragonwell8.10.11)(build1.8.0_322-b01)OpenJDK64-BitServerVM(AlibabaDragonwell8.10.11)(build25.322-b01,mixedmode

華為 bisheng 的是這樣的。

openjdkversion"1.8.0_322"OpenJDKRuntimeEnvironmentBiSheng(build1.8.0_322-b06)OpenJDK64-BitServerVMBiSheng(build25.322-b06,mixedmode

我們可以看出,它們不但修改了我們剛剛所說的那幾個配置參數,還在第二行的中間,加入了自己獨特的 JDK 名稱,很是酷炫。

那他們是怎麼做的呢?

別急,你都知道了 Version.java.template 文件可以生成最終的 Version 文件里的 print 方法,那你直接在方法裡,打印第二行的中間寫死一個字符串,不就行了麼?

當然可以,你的 JDK 你做主,但是,我們還是來看看他們是怎麼優雅地修改源碼的。

以騰訊 Kona 為例,它分別修改了如下地方的源碼。

一、改造了 Version.java.template 里的 print 代碼,但是沒有寫死字符串,而是像其他常量一樣,留了個占位符。

//https://github.com/Tencent/TencentKona-8/blob/master/jdk/src/share/classes/sun/misc/Version.java.templatepublicclassVersion{...privatestaticfinalStringjava_distro_name="@@java_distro_name@@";privatestaticfinalStringjava_distro_version="@@java_distro_version@@";publicstaticvoidprint(PrintStreamps){.../*Firstline:platformversion.*/.../*Secondline:runtimeversion(ie,libraries).*/ps.print(java_runtime_name+"("+java_distro_name+""+java_distro_version+")"+"(build"+java_runtime_version);.../*Thirdline:JVMinformation.*/...}}

二、改造了 GensrcMisc.gmk 文件,將這些占位符同樣用 sed 命令替換。

//https://github.com/Tencent/TencentKona-8/blob/master/jdk/make/gensrc/GensrcMisc.gmk$(JDK_OUTPUTDIR)/gensrc/sun/misc/Version.java\$(PROFILE_VERSION_JAVA_TARGETS):\$(JDK_TOPDIR)/src/share/classes/sun/misc/Version.java.template$(MKDIR)-p$(@D)$(RM)$@$@.tmp$(ECHO)Generatingsun/misc/Version.java$(callprofile_version_name,$@)$(SED)-e's/@@launcher_name@@/$(LAUNCHER_NAME)/g'\-e's/@@java_version@@/$(RELEASE)/g'\-e's/@@java_runtime_version@@/$(FULL_VERSION)/g'\-e's/@@java_runtime_name@@/$(RUNTIME_NAME)/g'\-e's/@@java_profile_name@@/$(callprofile_version_name,$@)/g'\-e's/@@java_distro_name@@/$(DISTRO_NAME)/g'\-e's/@@java_distro_version@@/$(DISTRO_VERSION)/g'\$<>$@.tmp$(MV)$@.tmp$@

三、改造了 spec.gmk.in 文件,在裡面做了變量的定義。

//https://github.com/Tencent/TencentKona-8/blob/master/common/autoconf/spec.gmk.in...#IncludeTencentJDKversioninformationDISTRO_NAME=TencentKona...DISTRO_VERSION=8.0.9

所以最終,這裡的 Tencent Kona 8.0.9 就顯示在了 java -version 信息里了。

其實本質上,就是改變最終 print 方法的代碼而已。

好了,關於如何搞出一套自己的 openJDK 發行版並做好 java -version 這個表面工作,你學廢了麼?

​​
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()