編譯環境
下面是最終編譯通過時的環境配置,中間踩了很多坑,光 boostrap JDK 就換過 1.6.0,1.7.0_71,1.7.0_80,1.8.0_131,1.7.0_40 等多個版本,主要是卡在 ant 和 JDK 版本不兼容,以及 openJdk和 boostrap JDK 不兼容等問題上
- 系統環境 :osx 10.11.6
- 編譯器 :clang
- openJdk :jdk7u-dev
- boostrap JDK :oracle JDK 7u40
- xcode version :8.0 (8A218a)
- ant version :1.8.0
- make 命令,請根據編譯環境自行調整
sudo make ALT_BOOTDIR=/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/contents/home SPOT_OS_VERSION_CHECK=OK LANG=C ANT_HOME=/Users/alberthumbert/Downloads/apache-ant-1.8.0 WARNINGS_ARE_ERRORS=false ALLOW_DOWNLOADS=true CC=clang COMPILER_WARNINGS_FATAL=false LFLAGS='-Xlinker -lstdc++' USE_CLANG=true LP64=1 LANG=C ARCH_DATA_MODEL=64 HOTSPOT_BUILD_JOBS=8
獲取源碼
- 本文不會有安裝和配置 brew , xcode 和 boostrap jdk(oracle jdk) 的內容,對于打算動手編譯jdk的人,這部分內容太基礎了,就不廢話了
- 首先確保 mercurial 已經安裝,mercurial 和 git 的作用差不多
sudo easy_install mercurial
- 使用 mercurial 提供的 hg 工具拉取遠程倉庫, 這里我拉取的是 jdk 7
hg clone http://hg.openjdk.java.net/jdk7u/jdk7u-dev
- 現在還未真正獲取到所有源碼,在進行下一步之前,建議先確保你有足夠的權限,在trust.rc中添加當前的用戶名
sudo vim /etc/mercurial/hgrc.d/trust.rc
- 一個可供參考的版本如下
[trusted]
users = alberthumbert
groups = root
- 接著正式獲取源碼,使用任何shell都可以, 如果執行后出現 xxx not trusted,說明上一步沒有設置好
sudo zsh ./get_source.sh
- 如果出現 abort: stream ended unexpectedly ,可能是網絡的問題,我這里試了很多次都沒有拉取成功,沒辦法只好走代理,在當前終端中設置代理,只需執行如下命令,只在當前終端生效
export all_proxy=socks5://[host]:[port] # 同時配置 http 和 https代理
- 如無意外,corba,jaxp,jaxws,langtools,jdk,hotspot 這些過程都將以 xxx files updated, 0 files merged, 0 files removed, 0 files unresolved 結束,不行就再試幾次
編譯環境
- 安裝llvm
brew install llvm
- 安裝 freetype
brew install freetype
- 安裝完freetype和llvm后需要設置鏈接
sudo ln -s /usr/bin/llvm-g++ /Applications/Xcode.app/Contents/Developer/usr/bin/llvm-g++
sudo ln -s /usr/bin/llvm-gcc /Applications/Xcode.app/Contents/Developer/usr/bin/llvm-gcc
- 安裝 ant , 編譯過程中提示ant版本太舊,在apache官網上下載新版本,然后設置鏈接,建議不要使用版本太高的ant,最好不要跟你的 OpenJdk 版本有太大差距
sudo ln -s Users/alberthumbert/Desktop/apache-ant-1.8.0/bin/ant /usr/bin/ant
- 安裝 XQuartz,在官網下載dmg并手動安裝 ,然后添加鏈接
sudo ln -s /opt/X11/include/X11 /usr/local/include/X11
- 注意如果上一步出現 operation not permitted ,說明你的os版本比較高,有些操作被系統保護,需要重啟 Command + R 進入 recover 模式,在終端中關閉保護, 輸入如下命令然后重啟即可
csrutil disable
配置參數
- 下面是一份可供參考的參數配置,可以寫在目錄的build.sh當中然后執行,注意jdk路徑和ant路徑需要根據實際情況自行設置
# 語言選項,必須設置,否則編譯好后會出現一個 HashTable 的 NPE錯
export LANG=C
# Bootstrap JDK 解壓路徑,必須設置
export ALT_BOOTDIR=/Library/Java/JavaVirtualMachines/jdk1.7.0_40.jdk/Contents/Home
export ANT_HOME=/Users/alberthumbert/Desktop/apache-ant-1.8.0
export ANT_VERSION=1.7.1
# 允許自動下載
export ALLOW_DOWNLOADS=true
# 并行編譯線程數
export HOTSPOT_BUILD_JOBS=4
export ALT_PARALLEL_COMPILE_JOBS=4
# 比較本次 build 出來的映像與先前版本的差異,對我們沒有意義
# 必須設置為 false,否則 sanity 檢查為報缺少先前版本 JDK 的映像的錯誤提示
export SKIP_COMPARE_IMAGE=false
# 使用預編譯頭文件,不加這個編譯會變慢
export USE_PRECOMPILED_HEADER=true
# 要編譯的內容
export BUILD_LANGTOOLS=true
export BUILD_HOTSPOT=true
export BUILD_JDK=true
# export BUILD_JAXWS=false
# export BUILD_JAXP=false
# export BUILD_CORBA=false
# 要編譯的版本
# export SKIP_DEBUG_BUILD=false
# export SKIP_FASTDEBUG_BUILD=true
# export DEBUG_NAME=debug
# 把它設置為 false 可以避開 javaws 和瀏覽器 Java 插件之類的部分的 build
BUILD_DEPLOY=false
# 把它設置為 false 就不會 build 出安裝包,因為安裝包里有奇怪的依賴
# 但即使不 build 出它也能得到完整的 JDK 映像,所以還是別 build
BUILD_INSTALL=false
export WARNINGS_ARE_ERRORS=false
export COMPILER_WARNINGS_FATAL=false
# 編譯結果所存放的路徑
export ALT_OUTPUTDIR=/Users/alberthumbert/jdk7u-dev/build_result
# 這兩個環境變量必須去掉,不然會發生奇怪的事情
# Makefile 檢查到這兩個變量就會提示警告
unset JAVA_HOME
unset CLASSPATH
make sanity
開始編譯
no boostrap dir
- 執行 make 進行編譯,編譯過程中可能提示找不到可用的 boostrap jdk ,雖然在上面已經配置過了,不太清楚為什么,只能再手動加上參數,注意這里也需要權限
sudo make ALT_BOOTDIR=/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/content/home
ant 版本過低
- 如果提示 ant 版本過低,但實際上你的 ant 又高于提示中的 ant 版本,有可能實際上是 boostrap jdk 版本太低,建議使用 oracle jdk 7u40 ,詳見后面踩坑記錄
ERROR:
The version of ant being used is older than
the required version of '1.7.1'.
The version of ant found was '1.7.0'.
中文 與 ascii 亂碼
- 自動生成的代碼(主要是AIDL文件)中注釋會帶有中文,這個時候由于配置中規定了使用 ascii ,這會導致編譯器無法識別而編譯不通過,解決辦法主要有兩種,一種使用shell命令或腳本,另一種直接修改文件
- 使用shell命令替換可以參考下面, 有可能不成功
sudo find /Users/alberthumbert/jdk7u-dev/build/macosx-x86_64/corba/gensrc/com/sun/ -name '*.java' | while read p; do native2ascii -encoding UTF-8 $p > tmpj; mv tmpj $p ; done
- 比較穩的做法是像這位老哥一樣修改文件,看上去很多其實也沒幾個文件,善用字符搜索很快就能搞定
clang 不支持 -fpch-deps
- 參考這一位老哥的做法,修改 hotspot/make/bsd/makefiles/gcc.make
# 注釋216-218行
# Flags for generating make dependency flags.
# ifneq ("${CC_VER_MAJOR}", "2")
# DEPFLAGS = -fpch-deps -MMD -MP -MF $(DEP_DIR)/$(@:%=%.d)
# endif
# 在218行下添加下面代碼
DEPFLAGS = -MMD -MP -MF $(DEP_DIR)/$(@:%=%.d)
ifeq ($(USE_CLANG),)
ifneq ($(CC_VER_MAJOR), 2)
DEPFLAGS += -fpch-deps
endif
endif
clang: error: no such file or directory: 'false'
- 仔細看發現是makefile生成的編譯命令中某個參數只剩了一個false,不知道是哪個參數,很蛋疼,順藤摸瓜找到生成參數的makefile
sudo vim /Users/alberthumbert/jdk7u-dev/hotspot/make/bsd/makefiles/vm.make
- 注釋掉下面個參數配置
#CFLAGS_WARN holds compiler options to suppress/enable warnings.
CFLAGS += $(CFLAGS_WARN/BYFILE)
# Do not use C++ exception handling
CFLAGS += $(CFLAGS/NOEX)
形參默認值問題
- 還是參考這位老哥,修改 hotspot/src/share/vm/code/relocInfo.hpp
//修改374行
inline friend relocInfo prefix_relocInfo(int datalen);
//修改469行
inline relocInfo prefix_relocInfo(int datalen = 0) {
assert(relocInfo::fits_into_immediate(datalen), "datalen in limits");
return relocInfo(relocInfo::data_prefix_tag, relocInfo::RAW_BITS, relocInfo::datalen_tag | datalen);
}
Undefined symbols for architecture x86_64: "_attachCurrentThread"
這個錯誤是在 debug_build 時才出現的,沒有保存出錯信息,簡單說一下情況,提示說 ThreadUtilities中的 getJNIEnv 和 getJNIEnvUncached 函數引用了一個不存在的函數 attachCurrentThread,之前寫過一點jni,感覺大概是一個封裝了jni線程調度函數的工具類出了問題
在osxapp目錄找到這個文件
cd /Users/alberthumbert/jdk7u-dev/jdk/src/macosx//native/sun/osxapp
- 實際上 ThreadUtilities.m 文件中可以找到這個函數,感覺是內聯出了問題,把 inline 關鍵字去掉即可通過編譯
inline void attachCurrentThread(void** env) {
if ([NSThread isMainThread]) {
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_4;
args.name = "AppKit Thread";
args.group = appkitThreadGroup;
(*jvm)->AttachCurrentThreadAsDaemon(jvm, env, &args);
} else {
(*jvm)->AttachCurrentThreadAsDaemon(jvm, env, NULL);
}
}
編譯通過
- 任務完成,耗時將近20分鐘,紀念一下
>>>Finished making images @ Sat Apr 7 14:22:59 CST 2018 ...
########################################################################
##### Leaving jdk for target(s) sanity all docs images #####
########################################################################
##### Build time 00:12:41 jdk for target(s) sanity all docs images #####
########################################################################
#-- Build times ----------
Target all_product_build
Start 2018-04-07 14:03:57
End 2018-04-07 14:22:59
00:00:16 corba
00:05:57 hotspot
00:00:02 jaxp
00:00:03 jaxws
00:12:41 jdk
00:00:03 langtools
00:19:02 TOTAL
-------------------------
- 下面讓我們來驗證一下編譯是否真的成功了
cd /Users/alberthumbert/jdk7u-dev/build/macosx-x86_64/j2sdk-image/bin
- 驗證一下版本
./java -version
#輸出
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-root_2018_04_07_14_03-b00)
OpenJDK 64-Bit Server VM (build 24.80-b07, mixed mode)
- 寫個 hello world, 用 ./javac Hello.java 編譯
public class Hello{
public static void main(String[] args){
System.out.println("Hello World !");
}
}
- 用 ./java Hello 運行
Hello World !
調試源碼
不單純為了編譯而去編譯,在學習jvm的同時能跟著代碼走才是最終目的
build debug_build fastdebug_build
- 注意 在普通的編譯模式下編譯出來的jdk是不能調試的,它跟你平時使用的普通jdk是一個東西,為了能夠支持調試 在make 命令后面需要加上 build_debug 參數,這時在 build/macosx-x86_64-debug/ 目錄可以找到另一個編譯版本,這里面的 jdk 和 hotspot是可調試的
- 細心的玩家可能還會發現一個 macosx-x86_64-fastdebug 目錄,如果你在配置文件中設置了 fastdebug 參數,那么就會有這個版本 jdk,簡單說一下 fastdebug 是什么,它是由于 jdk 的開發人員無法忍受編譯器極度緩慢的調試速度而打造的新調試版本,它的調試速度會比普通的 debug 快,不過替換掉了一些命令,所以它的表現和普通的 jdk 不太一樣_
使用 Xcode 調試運行
本來是打算使用 Clion 調試的,無奈 cmake 太難配,看了別人使用 xcode 很順暢,于是打開了萬年不用的 xcode
- 新建項目,名稱隨意,右鍵工作目錄,點擊 add files to (projectname),導入整個 jdk7u-dev,注意就是你用 hg 命令拉下來的整個倉庫,不是 macosx-x86_64-debug 目錄
- 點擊 product->scheme->edit scheme ,在 run -> info 中配置 executable 如下
/Users/alberthumbert/jdk7u-dev/build/macosx-x86_64-debug/bin/java
- 同時在這個 executable 的目錄下新建一個測試用的java文件,這里用回之前的 hello world
public class Hello{
public static void main(String[] args){
System.out.println("Hello World !");
}
}
- 在 run-> arguements 中配置參數為 hello,那么效果就是 ./java hello,所以你需要先 javac 一下
- 在運行之前找到 main.c ,沒錯,這個就是你所以 java 程序的入口,路徑在 /jdk/src/share/bin/main.c 找不到就 find . -iname main.c 一下
int
main(int argc, char **argv)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
{
int i = 0;
if (getenv(JLDEBUG_ENV_ENTRY) != NULL) {
printf("Windows original main args:\n");
for (i = 0 ; i < __argc ; i++) {
printf("wwwd_args[%d] = %s\n", i, __argv[i]);
}
}
}
JLI_CmdToArgs(GetCommandLine());
margc = JLI_GetStdArgc();
// add one more to mark the end
margv = (char **)JLI_MemAlloc((margc + 1) * (sizeof(char *)));
{
int i = 0;
StdArg *stdargs = JLI_GetStdArgs();
for (i = 0 ; i < margc ; i++) {
margv[i] = stdargs[i].arg;
}
margv[i] = NULL;
}
#else /* *NIXES */
margc = argc;
margv = argv;
#endif /* WIN32 */
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
FULL_VERSION,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
- 隨便打幾個斷點,點擊運行,先不要急著一頭扎進源碼,快速讓調試結束,如果終端輸出如下,恭喜你已經完成了所有任務
Hello World !
Program ended with exit code: 0

start the world
- 接下來我們調試 HotSpot,在hotspot目錄下的jni.cpp中找到 JNI_CreateJavaVM
/Users/alberthumbert/jdk7u-dev/hotspot/src/share/vm/prims/jni.cpp
- 也是隨便打上斷點,如果你上一步已經成功,那么現在所有的配置參數都不需要改變,直接調試即可
現在,開始你的深入理解 java 虛擬機之旅吧!
踩坑記錄
廢話很多,如果運氣好,上面的流程都通過了,下面就不用看了
ant 版本過低
ERROR: The version of ant being used is older than
the required version of '1.7.1'.
The version of ant found was '1.7.0'.
- 使用 ant -version ,如果發現版本確實過低,那么下載新版 ant 即可
- 然而我的機器上原先就沒有 1.7.0 版本的 ant,反復使用 homebrew 安裝了 ant,可以確定運行的 ant 版本是最新的,網上的說法是使用軟連接將ant鏈接過去,看了一下源碼,確實 openjdk 里面將 ant 目錄寫死在了/usr/bin/ant,問題是這個 ‘1.7.0’ 的ant到底是哪來的,為什么能找到這個版本的ant
sudo ln -s Users/alberthumbert/Desktop/apache-ant-1.10.3/bin/ant /usr/bin/ant
- 再看一下make sanity的輸出, 上面是我下載的ant,1.10.3 ,但下面的 ANT_VER 卻是 1.7.0,這么說來這個 ant 的版本可能跟我指定的 ant 沒有關系
ANT_HOME = /Users/alberthumbert/Desktop/apache-ant-1.10.3
…
ANT_VER = 1.7.0 [requires at least 1.7.1]
- 使用 mdfind 查找 這個ant version 是怎么來的,直接 mdfind “ANT_VER”
/Users/alberthumbert/jdk7u-dev/build.sh
/Users/alberthumbert/jdk7u-dev/jaxp/src/com/sun/org/apache/xalan/internal/xslt/EnvironmentCheck.java
/Users/alberthumbert/Desktop/apache-ant-1.10.3/manual/api/org/apache/tools/ant/MagicNames.html
/Users/alberthumbert/Desktop/apache-ant-1.10.3/manual/api/index-all.html
/Users/alberthumbert/Desktop/apache-ant-1.10.3/manual/api/constant-values.html
- 上面這個 EnvironmentCheck.java 很可疑,打開看下,有個檢查 ant 版本的方法,這里用到反射,理論上查找的就是 ANT_HOME 當中 ant.jar 里的類,還是不明所以
/** * Report product version information from Ant.
* * @param h Hashtable to put information in
*/
protected void checkAntVersion(Hashtable h)
{
if (null == h)
h = new Hashtable();
try
{
final String ANT_VERSION_CLASS = "org.apache.tools.ant.Main";
final String ANT_VERSION_METHOD = "getAntVersion"; // noArgs
final Class noArgs[] = new Class[0];
Class clazz = ObjectFactory.findProviderClass(ANT_VERSION_CLASS, true);
Method method = clazz.getMethod(ANT_VERSION_METHOD, noArgs);
Object returnValue = method.invoke(null, new Object[0]);
h.put(VERSION + "ant", (String)returnValue);
}
catch (Exception e)
{
h.put(VERSION + "ant", CLASS_NOTPRESENT);
}
}
- 嘗試更換 boostrap jdk 版本,這次使用 1.8.0 然后發現 make sanity 通過了,而輸出的配置列表中 ANT_VER 就等于 1.8.0
ANT_VER = 1.8.0 [requires at least 1.7.1]
- 下意識的想起之前使用的boostrap jdk 是1.7.0 而提示的 ANT_VER 就是1.7.0,看了下我的機子里還有 1.6.0 的 jdk ,那么用它來編譯一下… 黑人問號.jpg ,所以這個 ant 版本就等于 jdk 版本 ???WTF ???
ERROR: The version of ant being used is older than
the required version of '1.7.1'.
The version of ant found was '1.6.0.'.
- 總結一下,boostrap jdk 版本接近或者大于指定的ant版本,否則設定 ANT_HOME 沒有作用,但是網上很多解決辦法都是使用后者,可能是平臺差異,具體原因不明, 推薦使用Oracle JDK 7u40
Bootstrap JDK 版本過高
- 如果你嘗試使用 JDK 8 去編譯 OpenJDK 7 ,那么肯定是行不通的,在 OpenJDK 7 當中,許多 ant 的配置中都指定了 -werror 參數,這個參數意味著參與編譯的文件在編譯過程中導致的 warnning 都會被視為 error ,而在用 javac 編譯 java 文件時又由于 Bootstrap JDK 過高而出現大量的 warnning ,最典型的比如下面這個
主版本 52 比 51 新, 此編譯器支持最新的主版本。建議升級此編譯器
- 個人推薦 Oracle JDK 7u40 ,使用這個版本搭配 1.7.1 以上并接近這個版本的 ant
OS X 10.10 以上無法安裝 Oracle JDK
- 本來是覺得不需要寫這部分內容的,但mac上的 Oracle JDK 有個bug ,在安裝舊版本的 jdk 時你可能會收到這樣一條提示
Java from Oracle requires Mac OS X 10.7.3 or later.
Your system has Mac OS X Version 10.11.6. This product can be installed on Version 10.7.3 or later.
Visit java.com/help for more information.
MDZZ,多么清奇的腦回路,而且這個問題直到 JDK 8 的一些版本都存在,但是我們說過 Bootstrap JDK 版本不能過高,那么怎么安裝這個JDK呢,無奈只好幫Oracle 修一波 bug
在 dmg 加載上來之后,用下面命令解壓安裝包, 第一個路徑是你的 dmg 文件,第二個路徑是解壓目錄的目標地址,命名隨意
pkgutil --expand /Volumes/JDK\ 7\ Update\ 40/JDK\ 7\ Update\ 40.pkg
- 進入解壓目錄在 Distribution 文件中找到 pm_instal_check() 函數,用什么方法都好,總之讓它返回 true
function pm_install_check() {
//就是這么粗暴
return true;
}
- 重新打包, 然后用這個新的dmg進行安裝即可
pkgutil --flatten /Users/alberthumbert/Desktop/740.unpkg /Users/alberthumbert/Desktop/jdk740.pkg