版權聲明:本文爲北京尚學堂原創文章,未經容許不得轉載。前端
做爲一名程序猿 ,咱們天天都在寫Code,但你真的瞭解它的生命週期麼?今天就來簡單聊下它的生命歷程,提及一段Java Code,從出生到game over大致分這麼幾步:編譯、類加載、運行、GC。java
Java語言的編譯期實際上是一段「不肯定 」的過程,由於多是一個前端編譯器把.java文件轉變爲.class文件的過程;也多是指JVM的後端運行期編譯器(JIT編譯器)把字節碼轉變爲機器碼的過程;還多是指使用靜態提早編譯器(AOT編譯器)直接把.java文件編譯成本地機器碼的過程。可是在這裏咱們說的是第一類。也是符合咱們大衆對編譯認知的。編譯在這個時間段經歷了哪些過程呢?算法
詞法分析是將源代碼的字符流轉變爲Token集合,而語法分析則是根據Token序列抽象構造語法樹(ATS)的過程,ATS是一種用來描述程序代碼語法結構的樹形表示形式,語法樹的每一個節點都表明着程序代碼中的一個語法結構,例如包、類型、修飾符、運算符、接口、返回值甚至代碼註釋均可以是一個語法結構。數據庫
完成了語法和詞法分析以後,下一步就是填充符號表的過程,符號表中所登記的信息在編譯的不一樣階段都要用到。在這裏延伸一下符號表的概念。符號表是什麼呢?它是由一組符號地址和符號信息構成的表格,最簡單的能夠理解爲哈希表的K-V值對的形式。爲何會用到符號表呢?符號表最先期的應用之一就是組織程序代碼的信息。最初,計算機程序只是一串簡單的數字,但程序猿們很快發現使用符號來表示操做和內存地址(變量名)要方便得多。將名稱和數字關聯起來就須要一張符號表。隨着程序的增加,符號表操做的性能逐漸變成了程序開發效率的瓶頸,爲此從而誕生了許多提高序號表效率的數據結構和算法。至於所謂的數據結構和算法有哪些呢?大致說下:無序鏈表中的順序查找、有序數組中的二分查找、二叉查找樹、平衡查找樹(在這咱們主要接觸到的是紅黑樹)、散列表(基於拉鍊法的散列表,基於線性探測法的散列表)。像Java中的java.util.TreeMap和java.util.HashMap分別是基於紅黑樹和拉鍊法的散列表的符號表實現的。這裏提到的符號表的概念再也不細說,感興趣的能夠查找相關資料。編程
通過上兩步以後,咱們得到了程序代碼的抽象語法樹表示,語法樹能表示一個正確的源代碼抽象,但沒法保證源程序是符合邏輯的,這時候語義分析登場了,它的主要任務就是對結構上正確的源程序進行上下文有關性質的審查。標註檢查、數據及控制流分析、解語法糖是語義分析階段的幾個步驟,在這具體說下語法糖的概念。語法糖是指在計算機語言中添加的某種語法,這種語法對語言的功能並無影響,但更方便程序猿使用。Java中最經常使用的語法糖主要是泛型、變長參數、自從裝箱/拆箱、遍歷循環,JVM在運行時不支持這些語法,它們在編譯階段還原回簡單的基礎語法結構,這個過程也就是解語法糖。舉個泛型擦除的例子,List<Integer>和List<String>在編譯以後會進行泛型擦除,變成同樣的原生類型List<E>。後端
字節碼生成是Javac編譯過程的最後一個階段,在這個階段會把前面各步驟生成的信息轉化成字節碼寫到磁盤中,還會進行了少許代碼添加和轉換的工做。實例構造器<init>()方法和類構造器<clinit>()方法(這裏的實例構造器並非指默認構造函數,若是用戶代碼沒有提供任何構造函數,那編譯器將會添加一個沒有參數的、訪問性與當前類一致的默認構造函數,這個工做在填充符號表階段已經完成,而類構造器<clinit>()方法指的是編譯器自動收集類中的全部類變量賦值動做和靜態語句塊中的語句合併產生的)就是在這個階段添加到語法樹中的。到此爲止整個編譯過程結束。數組
編譯將程序編譯成字節碼以後,下一步就是類加載到內存的過程。安全
類加載的過程是在虛擬機內存的方法區進行,這地方涉及到虛擬機內存,因此在這首先簡單介紹下程序在內存區域分佈的概念。虛擬機內存區域劃分爲:程序計數器、棧、本地方法棧、堆、方法區(部分區域爲運行時常量池)、直接內存。微信
程序計數器是一塊較小的內存空間,它能夠看作是當前線程所執行的字節碼的行號指示器。在JVM概念模型中,字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令。網絡
棧用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。其中局部變量表存放了編譯期剋制的各類基本數據類型、對象引用。它與程序計數器同樣都是線程私有的。
本地方法棧與上面介紹的虛擬機棧做用類似,它們的區別不過是虛擬機棧爲虛擬機執行Java方法(字節碼)服務,而本地方法棧則爲虛擬機使用的Native方法服務,甚至有的虛擬機會把這兩塊合二爲一。
堆是JVM管理內存最大的一塊。它是被全部線程共享的一塊區域,它的惟一目的是存放對象實例,幾乎全部的對象實例都在這裏分配內存(像特殊的類對象會在方法區分配內存)。這地方也是垃圾收集管理的主要區域,從內存回收角度看,如今垃圾收集器都採用分代收集算法(後面會詳細介紹),因此Java堆還能夠進一步細分:新生代和老年代,而新生代進一步細分:Eden空間、From Survivor空間、To Survivor空間。爲了效率考慮,堆還可能劃分爲多個線程私有的分配緩衝區(TLAB)。不管如何劃分,都與存放內容無關,不管哪一個區域,存放的依然是對象實例,它們存在的目的只是爲了更好的回收和分配內存而已。
方法區與堆同樣,都是線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。而運行時常量池是方法區的一部分,它主要用於存放編譯期聲明各類字面量和符號引用。
直接內存並非虛擬機運行時數據區的一部分,也是不Java規範中定義的內存區域,你能夠簡單理解爲堆外內存,內存分配不受Java堆大小的限制但受整個內存大小的限制。
說完了虛擬機內存區域的概念,咱們回到正題,類加載的流程究竟是什麼呢?加載、驗證、準備、解析、初始化五步。其中加載、驗證、準備、初始化是順序執行的,而解析則不必定,它有可能會在初始化以後執行。
在加載階段,JVM須要完成三個步驟:首先經過類的全限定名來獲取定義此類的二進制字節流,而後將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構,最後在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據入口。在第一步獲取二進制字節流中並無明確指出從一個*.class文件中獲取,規定的靈活性致使咱們能夠從ZIP(爲JAR、EAR/WAR格式提供基礎)包中獲取,從網絡獲取(Applet),運行時計算生成(動態代理),其餘文件產生(JSP文件生成的Class類),從數據庫獲取。
驗證,顧名思義,其實就是爲了確保Class文件字節流中包含信息符合JVM的要求,由於Class文件的來源途徑不必定中規中矩的從編譯器產生,也有可能用十六進制編輯器直接編寫Class文件。校驗流程爲文件格式校驗、元數據驗證、字節碼驗證,這地方的具體安全校驗方式再也不細說。
準備階段正式爲類變量分配內存並設置初始值的階段,這些變量所使用的內存都在方法區進行分配。
解析階段是JVM將常量池內的符號引用替換爲直接引用(指向目標的指針、相對偏移量或句柄)的過程,前面咱們談到的編譯填充符號表的價值在這地方體現出來了。解析過程無非就是對類或接口、字段、接口方法進行解析。更多精彩內容關注微信公衆號:北京尚學堂(858568103)
類初始化階段是類加載過程的最後一步,在準備階段,變量已經賦過一次初始值,而在這一步,則會根據程序猿定製的要求進行初始化類變量和其餘資源。在這個階段就是執行前面編譯字節碼生成流程提到的<clinit>()方法的過程。虛擬機也保證在多線程環境下這個方法被同時調用時被正確的加鎖、同步,保證只有一個線程去執行這個方法而其餘線程阻塞等待。這地方還涉及到另外一個咱們比較關心的知識點,Java什麼時候觸發對類的初始化操做呢?
通過了上面兩個階段,程序開始正常跑起來了,咱們都知道程序執行過程涉及到了各類指令的計算操做, 程序如何執行的呢?這地方就會使用到文章開頭談到的後端編譯器(JIT即時編譯器)+解釋器這種搭配使用的混合模式(HotSpot虛擬機默認採用瞭解釋器與一個編譯器),字節碼執行引擎則負責着這類各類程序計算操做的任務,它在執行Java代碼的時候有可能會有解釋執行(經過解釋器執行)和編譯執行(經過即時編譯器產生本地代碼執行)兩種選擇,也可能二者兼備。棧幀是用於支持虛擬機進行方法調用和執行的數據結構,具體的壓棧彈棧各類指令計算的思路涉及到了一個經典的算法——Dijkstra算法,至於如何執行有興趣的本身查資料吧這地方不會過多深刻。運行期的優化問題在這個階段一樣重要,而JVM設計團隊則把對性能的優化集中到了這個階段,這樣可讓那些不是由Javac產生的Class文件一樣享受到編譯器優化帶來的好處,至於具體的優化技術有哪些呢?有不少,這裏簡單提幾個具備表明性的優化技術:公共子表達式消除、數組邊界檢查消除、方法內聯、逃逸分析等等。
也許你們對一段Java Code的生命史有點概念了,Java程序的生命史也就給你們介紹到這裏,從Jdk的安裝到配置,再到後面的項目編程,大體也就是如此,小編有幸在北京尚學堂學學Java,更多精彩內容關注微信公衆號:北京尚學堂。