本篇博客主要針對Java虛擬機的類加載機制,虛擬機字節碼執行引擎,早期編譯優化進行總結,其他部分總結請點擊Java虛擬總結上篇 。前端
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。java
類加載的時機不止一種:segmentfault
在初始化時,若待初始化的類有父類則其父類先進行初始化(接口除外),而且先初始化包含main的主類。須要注意的是子類引用父類非final靜態變量時,只初始化靜態變量所在類,即父類,而引用final類型static變量不會引發任何初始化,由於其編譯期間就已經儲存在常量池中了。另外數組定義也是不會引起類的初始化。好比後端
Student[] stus=new Student[10];
是不會引發Student類的初始化的。數組
經過類的全限定名來獲取定義此類的二進制字節流,將這個字節流所表明的靜態存儲結構轉換爲方法區的運行時數據結構,在內存中生成一個表明類的數據訪問入口的java.lang.Class對象。安全
驗證過程的目的是爲了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,而且不會危害虛擬機自身的安全。主要有數據結構
正式爲類變量分配內存並設置類變量初始值的階段,只包括類變量而不包括實例變量和final類變量,並且僅僅只是初始化爲0值。優化
虛擬機將常量池內的符號引用轉換爲直接引用的過程。符號引用用一組符號來描述所引用的目標。而直接引用是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。spa
在初始化階段真正開始執行Java程序代碼(字節碼),執行類的構造器<clinit>()方法,<clinit>()方法是由編譯器自動收集全部類變量的賦值動做和靜態語句塊的語句合併而成,同一類中的靜態塊與類變量按順序初始化,在同一個加載器下,一個類只會被初始化一次。插件
實現經過一個類的全限定名獲取描述此類的二進制字節流的代碼模塊稱爲類加載器。比較兩個類是否相等,必定是在同一個類加載器的前提下進行的,不然哪怕Class文件都同樣也不相等
<JAVA_HOME>\lib
目錄或-Xbootclasspath
參數指定的路徑中的類庫加載到內存中。<JAVA_HOME>\lib\ext
目錄或java.ext.dirs
系統變量指定的路徑中的全部類庫。classpath
)上的指定類庫,咱們能夠直接使用這個類加載器。通常狀況,若是咱們沒有自定義類加載器默認就是用這個加載器。
雙親委派模型工做過程是:若是一個類加載器收到類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每一個類加載器都是如此,只有當父加載器在本身的搜索範圍內找不到指定的類時(即ClassNotFoundException
),子加載器纔會嘗試本身去加載。
這樣作的好處是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係。
虛擬機的執行引擎自行實現,能夠自行制定指令集與執行引擎的結構體系。
棧幀是用於支持虛擬機進行方法調用和方法執行的數據結構,是虛擬機棧的棧元素。它儲存了方法的局部變量表,操做數棧,動態連接,方法返回地址,對於活動線程來講,只有棧頂的棧幀纔是有效的,稱爲當前棧幀,與其關聯的方法叫作當前方法。
局部變量表存放方法參數和方法內部定義的變量。單位是slot(槽),最大能夠達到32位。垃圾回收時,slot能夠複用,將不使用的變量置爲null是有意義的,方便垃圾回收。局部變量不像類變量,是沒有初始值的。
當虛擬機發現某個方法或代碼塊運行特別頻繁時,就會把這些代碼認定爲 「Hot Spot Code」(熱點代碼),爲了提升熱點代碼的執行效率,在運行時,虛擬機將會把這些代碼編譯成與本地平臺相關的機器碼,並進行各層次的優化,完成這項任務的正是 JIT 編譯器。
方法在編譯時並不肯定方法的真實地址,而是一個符號引用,使得Java的動態擴展能力提高,在類加載過程甚至運行時才肯定目標方法的直接引用。
在類的解析階段將一部分符號引用轉換爲直接引用,這部分符號引用表明的方法必須「編譯期可知,運行時不變」,如靜態方法,私有方法,實例構造器,父類方法。final方法也是。
靜態分派(與重載相關),依賴靜態類型來定位方法執行版本的分派動做。自動轉型順序:char->int->long->float->double->Character->Serializable->Object->char...
動態分派(重寫相關),找到操做數棧頂的第一個元素所指向的對象的實際類型,若常量池中的描述符和簡單名稱都相符,則返回直接引用,不然對其父類進行第二步。
動態分配的實現:
在類的方法區創建一個虛方法表提高效率,若子類未重寫父類的方法,則子類的繼承方法中地址和父類方法的地址是同樣的,若重寫了父類的方法,則子類的方法地址就會改變,指向本身實現的版本。如上圖Son的clone方法沒有被重寫,指向的是Object父類的地址,而hardChoice方法被重寫了,指向的是Son本身實現的地址。
類型檢查的主題過程在運行期而不是在編譯期,如Python,Javascript,Ruby,PHP,與之相對的就是靜態語言。
解釋執行爲邊解釋邊執行,編譯執行則是先將源代碼編譯成目標語言 (如: 機器語言) 以後經過鏈接程序鏈接到生成的目標程序進行執行。
三種編譯器:
標記是編譯過程的最小元素,關鍵字、變量名、字面量、運算符均可以成爲標記,詞法分析就是將源代碼的字符流轉變爲標記集合。
語法分析是根據Token序列構造抽象語法樹的過程。抽象語法樹是用來描述程序代碼語法結構的樹形表示方法,每個節點都表明着程序代碼的一個語法結構:包,類型,修飾符等。
相似編譯器的一種插件,若是插件對語法樹進行了修改,編譯器將回到解析及填充符號表的過程從新處理。
對語法抽象樹進行上下文有關性質的審查,如類型檢查。
將前面各個步驟生成的信息轉換成字節碼寫到磁盤中,類構造器<cinit>和實例構造器<init>就是在這個階段添加到語法樹中。
編譯器不會編譯if到達不到的語句,也就是取消分支不成立的代碼塊,能夠查看反編譯後的代碼驗證條件編譯。