談談JVM(基礎模型)

~鐵臂阿童木~發表於2020-07-12

一,基本概念

     JVM是可執行Java程式碼的假想計算機 ,包括一套位元組碼指令集、一組暫存器、一個棧、 一個垃圾回收,堆 和 一個儲存方法域。

    JVM 是執行在作業系統之上的,它與硬體沒有直接 的互動。

二,執行的過程

     先看記憶體模型

   

 

 

 

  我們都知道 Java 原始檔,通過編譯器,能夠生產相應的.Class 檔案,也就是位元組碼檔案, 而位元組碼檔案又通過Java虛擬機器中的直譯器,編譯成特定機器上的機器碼 。
  也就是如下:
  ① Java原始檔—->編譯器—->位元組碼檔案
  ② 位元組碼檔案—->JVM—->機器碼
  每一種平臺的直譯器是不同的,但是實現的虛擬機器是相同的,這也就是 Java 為什麼能夠 跨平臺的原因了 ,當一個程式從開始執行,這時虛擬機器就開始例項化了,

      多個程式啟動就會 存在多個虛擬機器例項。程式退出或者關閉,則虛擬機器例項消亡,多個虛擬機器例項之間資料不 能共享。

  程式計數器(執行緒私有)

    一塊較小的記憶體空間, 是當前執行緒所執行的位元組碼的行號指示器,每條執行緒都要有一個獨立的 程式計數器,這類記憶體也稱為“執行緒私有”的記憶體。
    正在執行 java 方法的話,計數器記錄的是虛擬機器位元組碼指令的地址(當前指令的地址)。如果還是Native方法,則為空。
    這個記憶體區域是唯一一個在虛擬機器中沒有規定任何OutOfMemoryError情況的區域。


  虛擬機器棧(執行緒私有)

    是描述java方法執行的記憶體模型,每個方法在執行的同時都會建立一個棧幀(Stack Frame) 用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊。

             每一個方法從呼叫直至執行完成 的過程,就對應著一個棧幀在虛擬機器棧中入棧到出棧的過程。
    棧幀( Frame)是用來儲存資料和部分過程結果的資料結構,同時也被用來處理動態連結 (Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨著方法呼叫而創
    建,隨著方法結束而銷燬——無論方法是正常完成還是異常完成(丟擲了在方法內未被捕獲的異 常)都算作方法結束。


  本地方法區(執行緒私有)
    本地方法區和Java Stack作用類似, 區別是虛擬機器棧為執行Java方法服務, 而本地方法棧則為 Native方法服務, 如果一個VM實現使用C-linkage模型來支援Native呼叫, 那麼該棧將會是一個 C棧,

             但HotSpot VM直接就把本地方法棧和虛擬機器棧合二為一。


  堆(Heap-執行緒共享)-執行時資料區
    執行緒共享的一塊記憶體區域,建立的物件和陣列都儲存在 Java 堆記憶體中,也是垃圾收集器進行 垃圾收集的最重要的記憶體區域。由於現代VM採用分代收集演算法,

             因此Java堆從GC的角度還可以 細分為: 新生代( Eden 區 、 From Survivor 區 和 To Survivor 區 )和老年代。


  方法區/永久代(執行緒共享)
    永久代(Permanent Generation), 用於儲存被 JVM 載入的類資訊、常量、靜 態變數、即時編譯器編譯後的程式碼等資料. HotSpot VM把GC分代收集擴充套件至方法區,

             即使用Java 堆的永久代來實現方法區, 這樣HotSpot的垃圾收集器就可以像管理Java堆一樣管理這部分記憶體, 而不必為方法區開發專門的記憶體管理器。

 

  執行時常量池(Runtime Constant Pool)

    Class檔案中除了有類的版 本、欄位、方法、介面等描述等資訊外,還有一項資訊是常量池(Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加 載後存放到方法區的執行時常量池中。

    Java虛擬機器對Class檔案的每一部分(自然也包括常量 池)的格式都有嚴格的規定,每一個位元組用於儲存哪種資料都必須符合規範上的要求,這樣才會 被虛擬機器認可、裝載和執行

 

 

  三 . 堆記憶體詳解

    Java堆從GC的角度還可以細分為: 新生代( Eden 區 、From Survivor 區 和To Survivor 區 )和老年代

 

 

 

  新生代
    是用來存放新生的物件。一般佔據堆的1/3空間。由於頻繁建立物件,所以新生代會頻繁觸發 MinorGC進行垃圾回收。新生代又分為 Eden區、ServivorFrom、ServivorTo三個區。
  1. Eden 區 Java新物件的出生地(如果新建立的物件佔用記憶體很大,則直接分配到老 年代)。當Eden區記憶體不夠的時候就會觸發MinorGC,對新生代區進行 一次垃圾回收。
  2. ServivorFrom 上一次GC的倖存者,作為這一次GC 的被掃描者。
  3. ServivorTo 保留了一次MinorGC過程中的倖存者。
  MinorGC的過程(複製->清空->互換)
  1 .eden 、 servicorFrom 複製到 ServicorTo ,年齡 + 1首先,把Eden和ServivorFrom區域中存活的物件複製到ServicorTo區域(如果有物件的年 齡以及達到了老年的標準,則賦值到老年代區),同時把這些物件的年齡+1(如果 ServicorTo 不 夠位置了就放到老年區);
  2 .清空 eden 、 servicorFrom 然後,清空Eden和ServicorFrom中的物件;
  3 .ServicorTo和ServicorFrom互換最後,ServicorTo和ServicorFrom互換,原ServicorTo成為下一次GC時的ServicorFrom 區。


  老年代
    主要存放應用程式中生命週期長的記憶體物件。
    老年代的物件比較穩定,所以 MajorGC 不會頻繁執行。在進行 MajorGC 前一般都先進行 了一次 MinorGC,使得有新生代的物件晉身入老年代,導致空間不夠用時才觸發。當無法找到足 夠大的連續空間分配給新建立的較大物件時也會提前觸發一次MajorGC進行垃圾回收騰出空間。
    MajorGC 採用標記清除演算法:首先掃描一次所有老年代,標記出存活的物件,然後回收沒 有標記的物件。MajorGC的耗時比較長,因為要掃描再回收。MajorGC 會產生記憶體碎片,為了減 少記憶體損耗,我們一般需要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的 時候,就會丟擲OOM(Out of Memory)異常。


  永久代
    指記憶體的永久儲存區域,主要存放 Class 和 Meta(後設資料)的資訊,Class 在被載入的時候被 放入永久區域,它和和存放例項的區域不同,GC 不會在主程式執行期對永久區域進行清理。所以這 也導致了永久代的區域會隨著載入的Class的增多而脹滿,最終丟擲OOM異常。

 

相關文章