《深入理解java虚拟机》第1章 走近Java
1.6實戰:自己編譯JDK
想要一探JDK內部的實現機制,最便捷的路徑之一就是自己編譯- -套JDK,通過閱讀和跟蹤調試JDK源碼去了解Java技術體系的原理,雖然門檻會高一點,但肯定會比閱讀各種書籍、文章更加貼近本質。另外,JDK中的很多底層方法都是本地化(Native) 的,需要跟蹤這些方法的運作或對JDK進行Hack的時候,都需要自己編譯套JDK?,F在網絡上有不少開源的JDK實現可以供我們選擇,如Apache Harmony. OpenJDK等??紤]到Sun系列的JDK是現在使用得最廣^泛的JDK版本,筆者選擇了OpenJDK進行這次編
譯實戰。
1.6.1 獲取JDK源碼
首先要先明確OpenJDK和Sun/OracleJDK之間,以及OpenJDK 6、OpenJDK 7、OpenJDK 7u和OpenJDK 8等項目之間是什么關系,這有助于確定接下來編譯要使用的JDK版本和源碼分支。從前面介紹的Java發展史中我們了解到OpenJDK是Sun在2006年末把Java開源而形成的項目,這里的“開源”是通常意義上的源碼開放形式,即源碼是可被復用的,例如IcedTea 9、UltraViolet9都 是從OpenJDK源碼衍生出的發行版。但如果僅從“開源”字面意義(開放可閱讀的源碼)上看,其實Sun自JDK 1.5之后就開始以Java Research License(JRL)的形式公布過Java源碼,主要用于研究人員閱讀(JRL許可證的開放源碼至JDK1.6 Update 23為止)。把這些JRL許可證形式的Sun/OracleJDK源碼和對應版本的OpenJDK源碼進行比較,發現除了文件頭的版權注釋之外,其余代碼基本上都是相同的,只有字體渲染部分存在一點差異,OracleJDK采用了商業實現,而OpenJDK使用的是開源的FreeType。當然,“相同”是建立在兩者共有的組件基礎上的,Oracle JDK中還會存在- - 些Open JDK沒有的、商用閉源的功能,例如從JRockit移植改造而來的Java Flight Recorder。預計以后JRockit 的MissionControl移植到HotSpot之后,也會以Oracle JDK專有、閉源的形式提供。
?Oracle的項目發布經理JoeDarcy在OSCON2011.上對兩者關系的介紹9也證實了OpenJDK7和OracleJDK7在程序上是非常接近的,兩者共用了大量相同的代碼(如圖1-6所示,注意圖中提示了兩者共同代碼的占比要遠高于圖形上看到的比例),所以我們編譯的OpenJDK,基本上可以認為性能、功能和執行邏輯上都和官方的Oracle JDK是-致的。
?
?是始于JDK 6時期,當時JDK 6和JDK 6 Update 1已經發布,JDK 7已經開始研發了,所以OpenJDK7是直接基于正在研發的JDK7源碼建立的。但考慮到OpenJDK7的狀況在當時還不適合實際生產部署,因此在OpenJDK 7 Build 20的基礎上建立了OpenJDK 6分支,剝離掉JDK 7新功能的代碼,形成-一個可以通過TCK 6測試的獨立分支。2012年7月,JDK 7正式發布,在OpenJDK中也同步建立了OpenJDK 7 Update項目對JDK 7進行更新升級,以及OpenJDK 8項目開始下一個JDK大版本的研發。按照開發習慣, .新的功能或Bug修復通常是在最新分支上進行的,當功能或修復在最新分支上穩定之后會同步到其他老版本的維護分支.上。OpenJDK 6、OpenJDK 7、OpenJDK 7u和OpenJDK 8的源碼都可以在它們相應的網頁上找到,在本次編譯實踐中,筆者選用的項目是OpenJDK 7u,版本為7u6。獲取OpenJDK源碼有兩種方式,其中一種是通過Mercurial代碼版本管理工具從
Repository中直接取得源碼(Repository 地址: htp://hg.openjdk java.net/jdk7u/jdk7u),獲取過程如以下代碼所示。
這是最直接的方式,從版本管理中看變更軌跡比看Release Note效果更好。但不足之處是速度太慢,雖然代碼總容量只有300 MB左右,但是文件數量太多,在筆者的網絡下全部復制到本地需要數小時。另外,考慮到Mercurial不如Git、SVN、ClearCase 或CVS之類的版本控制工具那樣普及,對于一般讀者,建議采用第二種方式,即直接下載官方打包好的源碼包,讀者可以從Source Bundle Releases頁面(地址: htp://jdk7 java.net/source.html)取得打包好的源碼,到本地直接解壓即可。一般來說,源碼包大概- - 至兩個月左右會更新--次,雖然不夠及時,但比起從Mercurial復制代碼的確方便和快捷許多。筆者下載的是OpenJDK 7 Update 6 Build b21版源碼包,2012 年8月28日發布,大概99MB,解壓后約為339MB.?
1.6.2系統需求
如果可能,筆者建議盡量在Linux、MacOS或Solaris上構建OpenJDK,這要比在Windows平臺上容易得多,本章實戰中筆者將以Ubuntu 10.10 和MacOS x 10.8.2 為例進行構建。如果讀者- -定要在 Windows平臺上完成編譯,可參考本書附錄A,該附錄是本書第- -版中介紹如何在Windows下編譯OpenJDK 6的例子,原有的部分內容現在已經過時了(例如安裝Plug部分),但還是有一-定參考意義,因此筆者沒有把它刪除掉,而是移到附錄之中。無論在什么平臺下進行編譯,都建議讀者認真閱讀- - 遍源碼中的README-builds .html文檔(無論在OpenJDK網站上還是在下載的源碼包中都有這份文檔),因為編譯過程中需要注意的細節非常多。雖然不至于像文檔上所描述的“Building the source code for the JDKrequires a high level of technical expertise. Sun provides the source code primarily for technical experts who want to conduct research. ( 編譯JDK需要很高的專業技術,Sun 提供JDK源碼是為了技術專家進行研究之用)”那么夸張,但是如果讀者是第一- 次編譯,那有可能會在一些小問題上耗費許多時間。在本次編譯中采用的是64位操作系統,編譯的也是64位的OpenJDK,如果需要編譯32位版本,那建議在32位操作系統上進行。在官方文檔上寫到編譯OpenJDK至少需要512MB的內存和600MB的磁盤空間。512MB的內存也許能湊合使用,不過600MB的磁盤空間估計僅是指存放OpenJDK源碼所需的空間,要完成編譯,600MB肯定是無論如何都不夠的,光輸出的編譯結果就有近3GB (因為有很多中間文件,以及會編譯出不同優化級別( Product、Debug、 FastDebug 等)的虛擬機),建議讀者至少保證5GB以上的空余磁盤。
對系統的最后一點要求就是所有的文件,包括源碼和依賴項目,都不要放在包含中文的目錄里面,這樣做不是一-定不可以,只是沒有必要給自己找麻煩。
1.6.3構建編譯環境
在MacOSe和Linux上構建OpenJDK編譯環境比較簡單(相對于Windows來說),對于Mac OS,需要安裝最新版本的XCode和Command Line Tools for XCode,在Apple Developer網站(https : //developer.apple.com/). 上可以免費下載, 這兩個SDK包提供了OpenJDK所需的編譯器以及Makefle中用到的外部命令。另外,還要準備一個6u14以上版本的JDK,因為OpenJDK的各個組成部分(Hotspot、JDK API、JAXWS、JX......有的是使用C++編寫的,更多的代碼則是使用Java自身實現的,因此編譯這些Java代碼需要用到-一個可用的JDK,官方稱這個JDK為“Bootstrap JDK"。如果編譯OpenJDK 7, Bootstrap JDK必須使用JDK6 Update 14或之后的版本,筆者選用的是JDK7 Update 4。最后需要下載-一個1.7.1 以上版本的Apache Ant,用于執行Java編譯代碼中的Ant腳本。對于Linux來說,所需要準備的依賴與Mac OS差不多,Bootstrap JDK和Ant都是- -樣的,在MacOS中GCC編譯器來源于XCodeSDK,而Ubuntu中GCC應該是默認安裝好的,需要確保版本為4.3以上,如果沒有找到GCC,安裝binutils即可,在Ubuntu 10.10下編譯OpenJDK 7u4所需的依賴可以使用以下命令- -次安裝完成。
sudo apt-get install build-essential gawk m4 openjdk-6-jdk
1 ibasound2-dev libcups2-dev 1 ibxrender-dev xorg-dev xutils-dev
x11proto-print-dev binutils libmotif3 libmotif-dev ant
1.6.4進行編譯
現在需要下載的編譯環境和依賴項目都準備齊全了,最后我們還需要對系統的環境變量做一些簡單設置以便編譯能夠順利通過。OpenJDK在編譯時讀取的環境變量有很多,但大多都有默認值,必須設置的只有兩個: LANG和ALT_ _BOOTDIR,前者是設定語言選項,必須設置為:
export LANG=C
否則,在編譯結束前的驗證階段會出現一個HashTable內的空指針異常。另外一個ALT_BOOTDIR參數是前面提到的Bootstrap JDK,在Mac OS上筆者設為以下路徑,其他操作系統讀者對應調整即可。
export ALT_ BOOTDIR=/Library/Java/JavaVirtualMachines/jdk1.7.0 _04.jdk/Contents/ Home
另外,如果讀者之前設置了JAVA_ HOME和CLASSPATH兩個環境變量,在編譯之前必須取消,否則在Makefile腳本中檢查到有這兩個變量存在,會有警告提示。
unset JAVA_ HOME
unset CLASSPATH
其他環境變量筆者就不再- -- 介紹了,代碼清單1-1給出筆者自己常用的編譯Shell腳本,讀者可以參考變量注釋中的內容。
????????????????????????????????????????代碼清單1-1環境變t設f
#語言選項,這個必須設置,否則編譯好后會出現一個HashTable的NPE錯
?
全部設置結束之后,可以輸入make sanity來檢查我們前面所做的設置是否全部正確。如果一切順利,那公幾秒鐘之后會有類似代碼清單1-2所示的輸出。
代碼清單卡2 make sanity檢查
~/Develop/JVM/jdkBuild/openjdk_7u4$ make sanity Build .Machine Information: buird machine = IcyFenix-RMBP.1ocaF Build Directory St ructure: CWD = /Users/IcyFenix/Develop/JVM/jdkBuild/openjdk_7u4 TOPDIR = _. LANGTOOES_TOPDIR = ./langtools JAXP_TOPDIR = ./jaxp JAXWS_TOPDIR = ./jaxws CORBA_TOPDIR = -./corba HOTSPOT_TOPDIR=./hotspat JDK_TOPDIR = ./jdk Build Directives: BUILD_LANGTOOLS = true BUILD_JAXP = true - - BUILD_JAXWS = true BUILD_CORBA = true BUILD_HOTSPOT =true DEBUG ELASSFILES = DEBUG BINARIES = ....因篇幅關系,中間省略了大量的輸出內..... OpenJDK-specific settings: FREETYPE HEADERS_PATH - /usr/X11R6/include ALT_FREETYPE HEADERS_PATH = FREETYPE LIB_PATH = /usr/X11R6/lib ALT_FREETYPE_LIB_PATH = Previous JDK Settings: PREVIOUS RELEASE PATH = US ING-PREVIOUS_RELEASE_IMAGE ALT_PREVIOUS_RELEASE_PATH = PREVIOUS_JDK_VERSION = 1.6.0 ALT_PREVIOUS_JDK_VERSION = PREVIOUS_JDK_FILE = ALT_PREVIOUS_JDK_FILE = PREVIOUS_JRE_FILE = ALT_PREVIOUS_JRE_FILE = PREVIOUS_RELEASE_IMAGE = /Library/Java/JavaVirtualMachines/jdk1.7.0_04.jdk/ Contents / Home ALT_PREVIOUS_RELEASE_IMAGE =Sanity check passed.
?Makefile的Sanity 檢查過程輸出了編譯所需的所有環境變量,如果看到“Sanity check passed.",說明檢查過程通過了,可以輸人“make"執行整個OpenJDK編譯( make不加參數,默認編譯make all),筆者使用Core i7 3720QM/ 16GB RAM的MacBook機器,啟動6條編譯線程,全量編譯整個OpenJDK大概需20分鐘,編譯結束后,將輸出類似下面的日志清單所示內容。如果讀者之前已經全量編譯過,只修改了少量文件,增量編譯可以在數十秒內完成。
#-- Build times ----------
編譯完成之后,進入OpenJDK源碼下的buildj2sdk-image目錄(或者build-debug. build-fastdebug這兩個目錄),這是整個JDK的完整編譯結果,復制到JAVA_ HOME目錄,就可以作為- -個完整的JDK使用,編譯出來的虛擬機,在-version命令中帶有用戶的機器名。
在大多數時候,如果我們并不關心JDK中HotSpot虛擬機以外的內容,只想單獨編譯
HotSpot虛擬機的話(例如調試虛擬機時,每次改動程序都執行整個OpenJDK的Makefile,
速度肯定受不了),那么使用hotspot/make目錄下的Makefile進行替換即可,其他參數設
置與前面是一-致的,這時候虛擬機的輸出結果存放在buildhotspot/outputdir/bsd_ amd64_
compiler2目錄9中,進人后可以見到以下幾個目錄。
這些目錄對應了不同的優化級別,優化級別越高,性能自然就越好,但是輸出代碼與源碼的差距就越大,難于調試,具體哪個目錄有內容,取決于make命令后面的參數。在編譯結束之后、運行虛擬機之前,還要手工編輯目錄下的env.sh文件,這個文件
由編譯腳本自動產生,用于設置虛擬機的環境變量,里面已經發布了“JAVA_ HOME、CLASSPATH、HOTSPOT_BUILD_ USER" 3個環境變量,還需要增加一個“LD_ LIBRARY_PATH",內容如下:
1.6.5 在IDE工具中進行源碼調試
在閱讀OpenJDK源碼的過程中,經常需要運行、調試程序來幫助理解。我們現在已經可以編譯出一個調試版本HotSpot虛擬機,禁用優化,并帶有符號信息,這樣就可以使用GDB來進行調試了。據筆者了解,許多對虛擬機了解比較深的開發人員確實就是直接使用GDB加VIM編輯器來開發、修改HotSpot的,不過相信大部分讀者更傾向于在IDE環境而不是純文本的GDB下閱讀、跟蹤HotSpot源碼,因此這節就簡單介紹一-下“如何在IDE中進行HotSpot源碼調試"。首先,到NetBeans網站(http://netbeans.org/) 上 下載最新版的NetBeans,下 載時選擇支持C/C++開發的那個版本。安裝后,新建-一個項目,選擇“基于現有源代碼的C/C++項
目”,在源碼文件夾中填入OpenJDK目錄下hotspot目錄的路徑,在下面的單選按鈕中選擇“定制”,如圖1-8所示,然后單擊“下一步”按鈕。
接著,在“指定構建代碼的方法”中選擇“使用現有的makefile",并填人Makefile文件的路徑(在hotspot/make目錄下),如圖1-9所示。單擊“下一步”按鈕,將“構建命令”修改為以下內容:?
${MAKE} -f Makefile clean jvmg
ALT_ BOOTDIR=/Library/Java/JavaVirtualMachines/jdk1.7.0_ 04. jdk/ Contents/ Home
ARCH_ DATA_ MODEL=64 LANG=C
?
OpenJDK 7u4源碼Makefile在終端運行時能正確獲取到系統指令集架構為64位,但在NetBeans中卻沒有取得正確的值,誤認為是32位,因此這里必須使用ARCH_ DATA_MODEE參數明確指定為64位。另外兩個參數ALT_ _BOOTDIR和LANG的作用前面已經介紹過。單擊“完成”按鈕,HotSpot 項目就這樣導人到NetBeans中了。不過,這時候HotSpot還運行不起來,因為NetBeans根本不知道編譯出來的結果放在哪里、哪個程序是虛擬機的入口等,這些內容都需要明確告知NetBeans。在HotSpot工程上單擊右鍵,
在彈出的快捷菜單中選擇“屬性”,在彈出的對話框中找到“運行”選項,設置運行命令為:
上面的Queens是MaKefile腳本自動產生的一段解八皇后問題的Java程序,用壬測試虛擬機,這里筆者直接拿來用了,讀者完全可以將它替換為自己的Java程序。讀者在調試Java代碼執行時,如果要跟蹤具體Java代碼在虛擬機中是如何執行的,也許會覺得無從下手,因為目前在HotSpot主流的操作系統上,都采用模板解釋器來執行字節碼,它與HT編譯器-樣,最終執行的匯編代碼都是運行期間產生的,無法直接設置斷點,所以HotSpot增加了以下參數來方便開發人員調試解釋器。
-XX: +TraceBytecodes - XX:StopInterpreterAt=<n>這組參數的作用是當遇到序號為<n>的字節碼指令時,便會中斷程序執行,進人斷點調試。在調試解釋器部分代碼時,把這兩個參數加到gamma后面即可。最后,還需要在“環境”窗日中設置環境變量,也就是前面env.sh腳本所設置的那幾個環境變量,如圖1-10 所示。
完成以上配置之后,一個可修改、編譯、調試的HotSpot工程就完全建立起來了,啟動器的執行入口是java.c的main()方法,讀者可以設置斷點單步跟蹤,如圖1-11所示。
由于HotSpot的源碼比較長,C/C++ 文件數量也很多,為了便于讀者閱讀,所以代碼清單1-3給出了各個目錄中代碼的主要用途,供讀者參考。?
?
?
?
1.7 本章小結
本章介紹了Java技術體系的過去、現在以及未來的一些發展趨勢,并通過實戰介紹了如何自己來獨立編譯-一個OpenJDK 7.作為全書的引言部分,本章建立了后文研究所必需的環境。在了解Java技術的來龍去脈后,后面章節將分為4部分去介紹Java在內存管理、Class文件結構與執行引擎、編譯器優化及多線程并發方面的實現原理。
?
總結
以上是生活随笔為你收集整理的《深入理解java虚拟机》第1章 走近Java的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Tensorflow Lite之编译生成
- 下一篇: 【译】Three Security Tr