在開始正式深刻學習HotSpot的源碼以前,你們首先須要明白HotSpot的源碼目錄結構是怎樣構成的,以及每個目錄中所包含的特定功能模塊實現是什麼。當只有在完全弄清楚這些問題後,才能更有針對性的閱讀和學習HotSpot的源碼。儘管HotSpot只是OpenJDK項目中的一個子集,可是HotSpot卻在整個OpenJDK項目中佔據了很是重要的地位,因此本書只會針對HotSpot項目的源碼進行講解,你們只需關注HotSpot便可。 java
在解壓後的HosSpot源碼目錄下,主要由agent、make、src和test這4個子目錄構成了HosSpot源碼的總體目錄結構。其中agent目錄下包含了Serviceability Agent的客戶端實現。make目錄下包含了用於build出HotSpot的各類配置文件。而src目錄則是其中最重要的一個子目錄,該目錄下包含了HotSpot的全部源碼實現,好比:與CPU的相關實現、與操做系統的相關實現、與平臺無關的通用實現、HotSpot的核心功能實現、各種GC的實現以及執行引擎的相關實現等。至於test目錄下僅僅只是包含了HotSpot相關的一些單元測試用例。 算法
在此須要提醒你們,儘管Java繼承了C語言的語法結構,並改編了C++語言的對象模型,可是彼此之間仍然存在較大的語法差別。因此Java開發人員在閱讀HotSpot的源碼時,千萬不要在語法細節上鑽牛角尖,只需關注具體的數據結構和算法便可。 編程
HotSpot的源碼目錄結構,以下所示(目錄整理自撒加大神的博文): 數據結構
├─agent Serviceability Agent的客戶端實現 框架
├─make 用於build出HotSpot的各類配置文件 jvm
├─src HotSpot的全部源代碼 數據結構和算法
│ ├─cpu CPU相關代碼 編程語言
│ ├─os 操做系相關代碼 函數
│ ├─os_cpu 操做系統+CPU的組合相關的代碼 工具
│ └─share 平臺無關的共通代碼
│ ├─tools 工具
│ │ ├─hsdis 反彙編插件
│ │ ├─IdealGraphVisualizer 將server編譯器的中間代碼可視化的工具
│ │ ├─launcher 啓動程序「java」
│ │ ├─LogCompilation 將-XX:+LogCompilation輸出的日誌(hotspot.log)整理成更容易閱讀的格式的工具
│ │ └─ProjectCreator 生成Visual Studio的project文件的工具
│ └─vm HotSpot VM的核心代碼
│ ├─adlc 平臺描述文件(上面的cpu或os_cpu裏的*.ad文件)的編譯器
│ ├─asm 彙編器接口
│ ├─c1 client編譯器(又稱「C1」)
│ ├─ci 動態編譯器的公共服務/從動態編譯器到VM的接口
│ ├─classfile 類文件的處理(包括類加載和系統符號表等)
│ ├─code 動態生成的代碼的管理
│ ├─compiler 從VM調用動態編譯器的接口
│ ├─gc_implementation GC的實現
│ │ ├─concurrentMarkSweep Concurrent Mark Sweep GC的實現
│ │ ├─g1 Garbage-First GC的實現(不使用老的分代式GC框架)
│ │ ├─parallelScavenge ParallelScavenge GC的實現(server VM默認不使用老的分代式GC框架)
│ │ ├─parNew ParNew GC的實現
│ │ └─shared GC的共通實現
│ ├─gc_interface GC的接口
│ ├─interpreter 解釋器,包括「模板解釋器」(官方版在用)和「C++解釋器」(官方版不在用)
│ ├─libadt 一些抽象數據結構
│ ├─memory 內存管理相關(老的分代式GC框架也在這裏)
│ ├─oops HotSpot VM的對象系統的實現
│ ├─opto server編譯器(又稱「C2」或「Opto」)
│ ├─prims HotSpot VM的對外接口,包括部分標準庫的native部分和JVMTI實現
│ ├─runtime 運行時支持庫(包括線程管理、編譯器調度、鎖、反射等)
│ ├─services 主要是用來支持JMX之類的管理功能的接口
│ ├─shark 基於LLVM的JIT編譯器(官方版裏沒有使用)
│ └─utilities 一些基本的工具類
└─test 單元測試
當你們清楚HotSpot的源碼目錄結構後,才能更快的熟悉HotSpot每個目錄下所包含的特定功能模塊實現。
2.2 Launcher簡介
Launcher是一種用於啓動JVM進程的啓動器。在Java中,Launcher能夠根據類別劃分爲2種。一種是正式版的啓動器,也就是你們在Windows平臺下常用到的java.exe和javaw.exe程序。前者在運行時會保留控制檯,以及顯示程序的輸出信息。然後者主要是用於執行Java的GUI程序,也就是說,使用javaw.exe執行Java程序時將不會顯示程序的輸出信息。關於Launcher的具體用法和標準選項配置,你們能夠在控制檯中輸入命令「java -help」,以下所示:
具體用法:
java [-options] class [args...](執行類)
或
java [-options] -jar jarfile [args...](執行jar文件)
其中選項包括:
-d32 |
使用32位數據模型(若是可用) |
-d64 |
使用64位數據模型(若是可用) |
-client |
選擇"client"VM |
-server |
選擇"server"VM |
-hotspot |
是"client"VM的同義詞[已過期]默認VM是client |
-cp |
目錄和zip/jar文件的類搜索路徑 |
-classpath |
目錄和zip/jar文件的類搜索路徑用「;」分隔的目錄,JAR檔案和ZIP 檔案列表, 用於搜索類文件 |
-D<name>=<value> |
設置系統屬性 |
-verbose[:class|gc|jni] |
啓用詳細輸出 |
-version |
輸出產品版本並退出 |
-version:<value> |
須要指定的版本才能運行 |
-showversion |
輸出產品版本並繼續 |
-jre-restrict-search|-no-jre-restrict-search |
在版本搜索中包括/排除用戶專用JRE |
-? -help |
輸出此幫助消息 |
-X |
輸出非標準選項的幫助 |
-ea[:<packagename>...|:<classname>] |
|
-enableassertions[:<packagename>...|:<classname>] |
按指定的粒度啓用斷言 |
-disableassertions[:<packagename>...|:<classname>] |
禁用具備指定粒度的斷言 |
-esa | -enablesystemassertions |
啓用系統斷言 |
-dsa | -disablesystemassertions |
禁用系統斷言 |
-agentlib:<libname>[=<options>] |
加載本機代理庫<libname>,例如-agentlib:hprof另請參閱 -agentlib:jdwp=help和-agentlib:hprof=help |
-agentpath:<pathname>[=<options>] |
按完整路徑名加載本機代理庫 |
-javaagent:<jarpath>[=<options>] |
加載Java編程語言代理, 請參閱java.lang.instrument |
-splash:<imagepath> |
使用指定的圖像顯示啓動屏幕 |
你們千萬不要認爲Launcher就是虛擬機實現,其實從嚴格意義上來講,Launcher只是一個封裝了虛擬機的執行外殼,由它負責裝載JRE環境和Windows平臺下的jvm.dll動態連接庫(Linux平臺下則是裝載libjvm.so)。在一個JVM的進程內部,只能執行一個指定的Java程序,也就是說,當執行多個Java程序時,也就意味着同時啓動了多個JVM進程。在1.5.6小節中,本書示例瞭如何編譯一個Debug版本的HotSpot,因此爲了調試跟蹤方面,你們可使用Java的另外一種啓動器gamma。在HotSpot中Launcher是使用C語言編寫的,對比gamma和java後不難發現二者的源碼幾乎是如出一轍的,僅存在少許差別,也就是說,在OpenJDK中gamma和java是共用的同一套Launcher源碼實現。gamma的源碼在HotSpot的源碼目錄下,你們能夠在/hotspot/src/share/tools/launcher/java.c中找到。而java卻並不是包含在HotSpot的源碼目錄下,而是包含在/jdk/src/share/bin/main.c中。
儘管Launcher並不是是HotSpot的核心,甚至應該算是HotSpot中比較「外圍」的功能模塊,但既然是這樣,筆者爲何還須要大費周章的對Launcher的源碼進行剖析?其實瞭解Launcher的執行原理是很是有意義的,Launcher既然是JVM的啓動器,那麼必然會由它負責調用HotSpot的核心代碼對JVM進行初始化,以及由它負責維護JVM的整個生命週期。因此理解了Launcher的執行原理,纔是邁進HotSpot的第一步。
2.3 跟蹤Launcher的執行過程
本書並不是只是一本單純講解HotSpot原理的理論性讀物,從本章開始,你們將會從以往的枯燥和乏味中深刻到HotSpot的具體實現細節上。對於那些曾經想要深刻研究JVM技術卻又止步於源碼面前的Java開發人員而言,本書的知識點將會是大家迫切想要獲得的答案。
儘管每個Java開發人員對Launcher的使用都很是熟悉,但這並不表明對Launcher的執行過程也瞭如指掌,因此本章將會重點講解關於Launcher的執行過程並剖析源碼細節。因爲HotSpot的Launcher是採用C語言編寫的,因此具有必定的C語言功底,必然是最好不過的。但若是你僅僅只是專攻於Java技術,也並不是沒法理解本章的一些源碼示例,畢竟Java的語法結構就是繼承自C語言,因此一些簡單C語法相信你也必定可以理解。在正式開始講解以前,筆者仍是須要再次重申一次關於源碼的閱讀方式,但願你們千萬不要過多停留在語法細節上,只需關注具體的數據結構和算法便可。
2.3.1 使用Launcher啓動JVM
Launcher從啓動到結束的整個執行鏈路,如圖2-1所示。當成功啓動Launcher後,會首先進入到Launcher的啓動函數中,這一點和Java程序同樣,Launcher的啓動函數一樣也是main()。main()函數的主要任務是負責建立運行環境以及啓動一個全新的線程去執行JVM的初始化和調用Java程序的main()方法。
圖2-1 Launcher的執行過程
當main()函數成功建立運行後,就會啓動一個全新的線程去調用JavaMain()函數,而JavaMain()函數的主要任務是負責調用InitializeJVM()函數。顧名思義InitializeJVM()函數確定會負責JVM初始化的相關工做,但InitializeJVM()函數自己卻並不具有初始化JVM的能力,而是由它調用本地函數JNI_CreateJavaVM()去完成真正意義上的JVM初始化。
當JVM初始化完成後,Launcher接着調用LoadClass()函數和GetStaticMethodId()函數,分別獲取Java程序的啓動類和啓動方法。當這2個步驟執行完後,Launcher就會調用本地函數jni_CallStaticVoidMethod()執行Java程序的main()方法。
最後Launcher還會調用本地函數jni_DetachCurrentThread()斷開與主線程的鏈接,當成功與主線程斷開鏈接後,Launcher就會一直等待程序中全部的非守護線程(non-daemon thread)所有執行結束,而後調用本地函數jni_DestroyJavaVM()對JVM執行銷燬。在此須要提醒你們,在JDK1.2版本以前,只有主線程才容許對JVM執行銷燬,而在JDK1.2及後續版本中則沒有此限制,非主線程也容許對JVM執行銷燬。
2.3.2 啓動函數main()
當Launcher成功啓動後,首先會進入到main()函數中對與運行環境相關的局部變量進行初始化。初始化後的局部變量不只在程序後續建立運行環境時須要使用到,在調用JavaMain()函數時,也須要將這些變量傳遞過去,以下所示:
代碼2-1 初始化與運行環境相關的局部變量
/* 初始化與運行環境相關的局部變量 */ |