天天看點

談談JVM(基礎模型)

一,基本概念

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

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

二,運作的過程

     先看記憶體模型

談談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 區 )和老年代

談談JVM(基礎模型)

  新生代

    是用來存放新生的對象。一般占據堆的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異常。