JVM知識梳理與總結上

1、編寫Java代碼到運行它

      如上圖XXX.java是源文件,然後使用命令javac  XXX.java將源文件編譯成XXX.class文件,然後再使用命令java XXX字節碼文件(class文件)。當然實際工作中一般項目中是使用mvn相關命令將java系統打包成jar/war包,然後使用java xxx.jar或者部署到tomcat等相關web服務器運行。JVM在執行字節碼文件的時候首先類加載器要先將字節碼文件加載到JVM中,然後再由JVM來執行我們的代碼。

     class字節碼文件從加載到執行結束大概經歷瞭如下幾個階段:

   

1.1、加載階段

    通過一個全限定類名來獲取定義此類的二進制字節流;將這個字節流的靜態存儲結構轉化爲方法區的運行時數據結構;在內存中生成一個代表該類的Class對象,作爲方法區這個類的各種數據的訪問入口。存放的內存有的叫方法區、有的也叫永久代區(JDK8後叫Metaspace元數據空間)。

1.2、驗證階段

   根據Java虛擬機規範,來校驗你加載進來的“.class”文件中的內容是否符合指定的規範。以確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並不會危害虛擬機自身的安全。主要包括文件格式驗證、元數據驗證、字節碼驗證、符號引用驗證。

1.3、準備階段

    給類分配一定的內存空間,然後給他裏面的類變量(也就是static修飾的變量)分配內存空間,並指定一個默認的初始值。

1.4、解析階段

    虛擬機將常量池內的符號引用替換爲直接引用的過程(符號引用類似於別名,直接引用類似於實際存儲地址)。主要包括類或接口的解析、字段解析、類方法解析、接口方法解析。

1.5、初始化階段

    執行類的初始化代碼,如果初始化的時候發現其父類還沒被初始化那麼會先初始化其父類。

 

2、重點關注類的加載

2.1、類加載器

2.1.1、啓動類加載器(Bootstrap ClassLoader)

    加載<JAVA_HOME>\lib目錄中的類庫(或被-Xbootclasspath參數所指定的路徑中)


2.1.2、擴展類加載器(Extension ClassLoader)

   加載<JAVA_HOME>\lib\ext目錄中的類庫(或被java.ext.dirs系統變量指定的路徑中的類庫)


2.1.3、應用程序類加載器(Application ClassLoader)

    加載用戶類路徑(ClassPath)上所指定的類庫。


2.1.4、用戶自定義加載器

    繼承ClassLoader  ;重寫findClass()方法 ;調用defineClass()方法

 

2.2、雙親委派機制

    你的應用程序類加載器需要加載一個類,他首先會委派給自己的父類加載器去加載,最終傳導到頂層的類加載器去加載,但是如果父類加載器在自己負責加載的範圍內沒找到這個類,那麼就會下推加載權利給自己的子類加載器。

 

3、JVM內存區域劃分

3.1、方法區(JDK8以後改爲Metaspace)

    存儲了每個類的信息(包括類的名稱、方法信息、字段信息)、靜態變量、常量以及編譯器編譯後的代碼等。常見的class字節碼文件加載到JVM後就是存放在方法區的。


3.2、程序計數器

    簡單說就是我們寫好的java源文件被編譯成class後綴的字節碼文件,而class字節碼文件就對應計算機能識別的一條條指令。所以當JVM加載.class文件到內存後,就會使用字節碼執行殷勤去執行我們寫好的代碼編譯成字節碼對應的一條條指令。
所以程序計數器就是用來記錄當前執行的字節碼指令的位置的,也就是記錄目前執行到了哪一條字節碼指令。


3.3、java虛擬機棧

    虛擬機棧中存放每個方法執行時創建的棧幀,對於執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱爲當前棧幀,
這個棧幀所關聯的方法稱爲當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。棧幀用於存放局部變量表、操作數棧、動態鏈接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,並且寫入了方法表的 Code 屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決於具體的虛擬機實現。
    每個線程都有自己的Java虛擬機棧,比如在執行main方法的main線程就會有自己的一個Java虛擬機棧,用來存放自己執行的那些方法的局部變量。如果線程執行了一個方法,就會對這個方法調用創建對應的一個棧幀。棧幀裏就有這個方法的局部變量表 、操作數棧、動態鏈接、方法出口等東西。

3.4、本地方法區

    本地方法棧則爲使用到的本地操作系統(Native)方法服務。在JVM規範中,並沒有對本地方發展的具體實現方法以及數據結構作強制規定,虛擬機可以自由實現它。

 

3.5、堆

   Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。 此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”(Garbage Collected Heap)。

    從內存回收的角度來看,由於現在收集器基本都採用分代收集算法,所以Java堆中還可以細分爲:新生代和老年代;從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)。不過無論哪個區域,存儲的都仍然是對象實例,進一步劃分的目的是爲了更好地回收內存,或者更快地分配內存。如果在堆中沒有內存完成實例分配,並且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
 

3.6、堆外內存

  在JDK 1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然後通過一個存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著提高性能,因爲避免了在Java堆和Native堆中來回複製數據。通過NIO中的allocateDirect這種API,可以在Java堆外分配內存空間。然後,通過Java虛擬機裏的DirectByteBuffer來引用和操作堆外內存空間。

 顯然,本機直接內存的分配不會受到Java堆大小的限制,但是,既然是內存,肯定還是會受到本機總內存(包括RAM以及SWAP區或者分頁文件)大小以及處理器尋址空間的限制。服務器管理員在配置虛擬機參數時,會根據實際內存設置-Xmx等參數信息,但經常忽略直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操作系統級的限制),從而導致動態擴展時出現OutOfMemoryError異常。
 

4、JVM各個內存區域協作流程圖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章