標籤:
引子:開啟黑匣子 心中有數
老實說,對於C++的整個編譯運行過程,我並沒有全面的瞭解,好幾次被問住了,看來是彙編沒有學好,但是在看完《深入Java虛擬機器》之後,對於Java代碼到啟動並執行每一個細節,有了更全面的認識。
描述一下整體的流程:程式員根據Java API編寫Java程式,各種類檔案,用一個Java編譯器編譯Java程式為class檔案,class檔案通過一定的分發方式被Java虛擬機器裝載、串連、初始化,變成Java虛擬機器方法區的Class類,然後被執行個體化為堆區的對象,隨著虛擬機器的執行,對象可能不再被引用而被記憶體回收,隨後class檔案可能變得不可觸及而被虛擬機器卸載,最後虛擬機器隨著最後一個非背景線程的結束而退出。
步驟一:編寫Java程式【產生 *.java】
一個Java檔案只允許定義一個public的Java類,因為虛擬機器是通過檔案名稱的機制搜尋類型定義的。Java程式員根據Java API編寫程式。
步驟二:編譯Java檔案為class檔案【產生class】
class檔案的結構包含了Java虛擬機器所需要知道的關於類的所有資訊。檔案內容大致為:魔術、版本、常量池、this_class、super_class、介面、欄位、方法、屬性
class檔案稍微有點繞的是常量池。常量池中的常量不是我們程式員角度理解的狹義的常量,一個類的名字是常量,一個方法的描述是常量,一個屬性的描述也是常量。常量池中包含了所有的字面資訊,通過索引,你可以尋找到一個方法的名字和描述,一個欄位的名字,一個常量字串 等等。在串連模型中,有一個解析的過程,就是因為在位元組碼中,所有的都是符號引用,比如調用了某個方法,位元組碼是說調用了常量區的一項內容,這項內容表示的是一個方法,因此,這就需要進行解析,將這個常量區的引用轉變為方法的直接引用。常量池中被解析的項都會被標記,這樣下一次就不用再次解析了。
所以我們看class檔案是如何完全表達一個類的資訊的:類的名字通過this_class指向的常量池可以得到,父類通過super_class,介面、方法、欄位 都可以通過相應的檔案段在常量區中找到基本資料。方法的位元組碼在方法的屬性欄位中定義了位元組碼,方法的屬性還定義了有多少個變數,需要用多大的棧空間。
步驟三:裝載、串連、初始化【產生類】
在最開始我們強調通過一定的分發方式,這是因為class檔案可能存在與啟動並執行虛擬機器上,也可能存在與網路上,通過使用者自訂的類型裝載器,開發人員可以定義靈活的機制擷取代碼。
1,裝載。裝載必須產生代表該類型的二進位流(讀檔案或者從網路或者其他方式),解析位元據為內部資料結構,建立一個 java.lang.Class類
2,驗證。驗證是否符合Java語言的語義。
3,準備。為類變數分配記憶體,並初始化為預設值。
4,解析。就是在類型的常量池中尋找類、介面、欄位和方法的符號引用,把他們替換為直接引用的過程。在符號引用被首次使用之前,解析都是可選的。
5,初始化。初始化為“正確”的初始值,即程式員期望的初始值,代碼中static定義的,被收集到 <clinit> 方法中。
步驟四:類執行個體產生,記憶體回收【產生類執行個體:對象】
現在一切具備,程式開始運行。記憶體分別如
到此時,我想我們對整個流程有了一個全面的認識(我們刻意忽略了安全相關的話題)
從虛擬機器的角度,最開始是面對的class檔案,讀入並解析class檔案,將PC寄存器設定為main入口,然後開始執行位元組碼。位元組碼總是針對運算棧的,所以虛擬機器知道運算元就在運算棧那裡。位元組碼由操作符和運算元定義,運算元可能為Java棧的局部變數,可能為常量池的引用。如果是常量池的引用,並且還未解析,則需要進行解析。任何時候,棧中都是基本變數或者對象引用,按部就班執行就行了。
深入Java虛擬機器(程式運行)