聽說阿里某程序員對書法十分感興趣,退休後決定在這方面有所建樹。因而花重金購買了上等的文房四寶。java
一日,飯後突生雅興,一番磨墨擬紙,並點上了上好的檀香,很有王羲之風範,又具顏真卿氣勢,定神片刻,潑墨揮毫,鄭重地寫下一行字:hello world。程序員
固然了,這是個專屬程序員的段子哈哈哈。面試
那麼問題來了,寫了這麼久的Hello World,你們肯定本身瞭解本身寫的東西背後是什麼原理嗎?(o≖◡≖)編程
【給出2分鐘,該知識點涉及到了Java程序執行流程,包括編譯、加載和執行,你是否可以理清呢?】安全
接下來進入嚴肅時間 (@ ̄ー ̄@)函數
public class Main { private static String word = "Hello World!"; public static void main(String[] args) { new Main().say(); } private void say() { System.out.println(word); } }
整個代碼的執行過程能夠分爲三個階段:工具
代碼編譯的做用就是將咱們編寫的 Main.java文件轉化爲Main.class文件,.class在這裏又被稱爲字節碼文件,打開就是一堆的火星文【反正就是看不懂】,在這裏咱們能夠將編譯的過程看做生產JVM原料的過程,使用的工具就是jdk提供的工具javac。 大體流程以下:優化
這個過程對咱們來講實際上是徹底屏蔽的,可是實際上它是現代經典編譯原理的 套路,詞法分析也是爲了給後面編譯作準備的】操作系統
在咱們眼中,Main.java已經能夠清晰理解到底寫的是什麼東西了,可是對於JVM來講仍是一臉懵逼的,因此才須要構建成語法樹,在這一步後就不會再對源碼文件進行操做了,後續的操做都創建在抽象語法樹上翻譯
填充符號表,符號表是由一組符號地址和符號信息構成的表格,這個表格在編譯的不一樣階段都會被用到,如在目標代碼生成階段,會對符號名進行地址分配,而符號表就是地址分配的依據。
語義分析,語義分析階段也能夠說是語義檢測階段,上面說到語法分析會構建一棵語法樹,那麼這棵語法樹是不是正確合理的,就由語義分析來作了,語義分析會經過標註檢查和數據及控制流分析檢查兩步入手,在生成字節碼的最後一步信息把關。
字節碼生成,這是javac編譯過程的最後一個階段了,字節碼生成階段並不僅是簡簡單單的將前面各個步驟生成的信息轉化成字節碼而後放入磁盤中,還進行了少許的代碼添加和轉換工做,如咱們本身重載的構造函數。
編譯期到這裏就結束了,那麼由誰來將這些原料傳輸給JVM虛擬機呢?這個時候就要看看類加載的過程了。
類加載簡單來講就是將由類加載器將編譯後的字節碼文件【Main.class】加載到虛擬機中 ,那麼天然而然的,要先介紹下四種類加載器
說說四種類加載器
能夠從上圖中看出,類加載器能夠分爲四種,而第四種是由咱們本身實現的,那麼其餘三種由JVM提供的類加載在咱們啓動該Main程序的過程當中起到了什麼做用呢?
首先說說啓動類加載器 Bootstrap ClassLoader ,啓動類加載器的做用主要是加載 %JAVA_HOME%\jre\lib\rt.jar 類庫,將其加載到虛擬機內存中,那麼rt.jar類庫到底有什麼做用呢?rt.jar下包含了Java的基礎類庫,也就是Java doc裏面看到的全部的類的class文件,感興趣的朋友能夠本身打開目錄看下。
其次是擴展類加載器 Extension ClassLoader ,擴展類加載器的做用主要是負責加載JAVA_HOME\jre\lib\ext目錄下的全部類庫,主要是載入擴展包。
再者是系統類加載器 Application ClassLoader, 也稱之爲應用程序類加載器,負責加載用戶類路徑(也就是咱們配置的CLASSPATH)上所指定的類庫,是應用程序中默認的類加載。
看完以上三個類加載器的簡單描述過程,是否是有種終於知道了咱們配置的jdk環境的最終做用了吧,是的,就是讓類加載器識別到後加載各類類庫。
那麼問題來了?是哪一個類加載器加載了咱們的Hello World程序呢?是的,就是應用程序中默認的類加載器 Application ClassLoader。
知道了類加載器後,那接下來總要了解下類加載器怎麼加載的吧?
說說類加載的過程
網上找了張圖片,簡單明瞭。雖然說是簡單明瞭,不過確實異常重要的,由於是面試熱點(✿◡‿◡)
其實就是上文說到的系統類加載器 Application ClassLoader將編譯後的Main.class文件加載到內存中。
【思考】拋出個問題,所謂的加載到內存中,咱們都知道JVM把內存分紅了幾大模塊,那麼請問是加載到哪一個模塊中?熱點面試題,答案見文末!
連接中包含了三部曲,總的做用就是負責將Main.class的二進制數據合併到JRE中。
關於三部曲,其實很好理解;
首先是驗證階段,類加載器將二進制字節流加載到虛擬機中,確定是須要進行驗證的,避免危害虛擬機自身安全,而這也是驗證階段存在的價值;
接下來是準備階段,準備階段是正式爲類變量分配內存而且設置類變量默認值的地方,好比上面HelloWorld程序中的
private static String word = "Hello World!";
注意我描述的第一個是類變量,也就是static所描述的變量,其次是默認值,也就是上面的word的默認值null,若是是數字則爲0。
最後是解析階段,解析階段的做用主要是將常量池內的符號引用替換爲直接引用的過程,解析階段其實有點難理解,至少是比上面的兩個階段要難理解的,我這裏儘可能直白點;
所謂的符號引用指的是包含了類的信息、方法名、方法參數等信息的字符串,而當第一次運行時,JVM會根據這行字符串去檢索到對應的方法入口,而爲了下次不用再作一樣的檢索,在第一次運行的時候就會將符號引用替換成直接引用,這樣後面就能夠省去必定的消耗了;這裏的直接引用其實就是指偏移量,虛擬機能夠經過偏移量直接找到方法入口,再也不須要作檢索了。
初始化 終於來到初始化階段了,上面咱們有說到word默認值是null,是系統賦的默認值,而在初始化階段,則是根據咱們人爲的初始化類變量和其餘資源,好比上面的word則被我初始化成了"Hello World!"。
上面說到Main.class被加載到了Java虛擬機內存中,那麼接下來即是執行的過程了。那麼由誰來執行這一過程呢?
實際上,一個Java虛擬機在運行的時候能夠劃分爲三個子系統:
很明顯、很清晰,圖中的類加載子系統在上面已經談了,執行引擎子系統就是負責執行這一部分的,那麼過程是怎麼樣的呢?
其實很簡單,執行引擎會把字節碼轉換爲機器碼【what?居然還要轉換。拜託<(ˉ^ˉ)>,字節碼是被JVM識別的語言,字節碼纔是最終被操做系統識別的語言】
而後操做系統才能夠真正調用,不少學或者作Java的人都聽過JIT,可是都不知道具體是幹嗎的,沒錯說的就是你。
這裏終於能夠解釋下了,字節碼轉換成機器碼的翻譯工做使用的就是JIT(Just In Time)即時編譯器(對熱代碼整段編譯)和Java字節碼解釋器(一行一行解釋字節碼)來完成的。 這裏給下JIT編譯的工做流程:
JVM字節碼 -> 機器無關優化 -> 中間代碼 -> 機器相關優化 -> 中間代碼 -> 寄存器分配器 -> 中間代碼 -> 目標機器碼生成器 -> 目標機器碼
最後執行引擎會找到main()這個入口方法,而且執行其中的字節碼指令。
**【思考解惑】**加載階段完成後,虛擬機會將Main.class的二進制字節流按照虛擬機所需的格式存儲在方法區之中,而後在內存中實例化一個java.lang.Class類的對象,做爲程序訪問方法區中的這些類型數據的外部接口,實例化後的java.lang.Class類的對象也是存放在方法區中的。