您好,登錄后才能下訂單哦!
1 基本概念:
JVM 是可運行 Java 代碼的假想計算機 ,包括一套字節碼指令集、一組寄存器、一個棧、 一個垃圾回收,堆 和 一個存儲方法域。JVM 是運行在操作系統之上的,它與硬件沒有直接 的交互。
2 運行過程:
我們都知道 Java 源文件,通過編譯器,能夠生產相應的.Class 文件,也就是字節碼文件, 而字節碼文件又通過 Java 虛擬機中的解釋器,編譯成特定機器上的機器碼 。 也就是如下: ① Java 源文件—->編譯器—->字節碼文件 ② 字節碼文件—->JVM—->機器碼 每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的,這也就是 Java 為什么能夠 跨平臺的原因了 ,當一個程序從開始運行,這時虛擬機就開始實例化了,多個程序啟動就會 存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不 能共享。
2.1.線程
這里所說的線程指程序執行過程中的一個線程實體。JVM 允許一個應用并發執行多個線程。 Hotspot JVM 中的 Java 線程與原生操作系統線程有直接的映射關系。當線程本地存儲、緩 沖區分配、同步對象、棧、程序計數器等準備好以后,就會創建一個操作系統原生線程。 Java 線程結束,原生線程隨之被回收。操作系統負責調度所有線程,并把它們分配到任何可 用的 CPU 上。當原生線程初始化完畢,就會調用 Java 線程的 run() 方法。當線程結束時, 會釋放原生線程和 Java 線程的所有資源。 Hotspot JVM 后臺運行的系統線程主要有下面幾個:
2.2.JVM 內存區域
JVM 內存區域主要分為線程私有區域【程序計數器、虛擬機棧、本地方法區】、線程共享區 域【JAVA 堆、方法區】、直接內存。 線程私有數據區域生命周期與線程相同, 依賴用戶線程的啟動/結束 而 創建/銷毀(在 Hotspot VM 內, 每個線程都與操作系統的本地線程直接映射, 因此這部分內存區域的存/否跟隨本地線程的 生/死對應)。 線程共享區域隨虛擬機的啟動/關閉而創建/銷毀。 直接內存并不是 JVM 運行時數據區的一部分, 但也會被頻繁的使用: 在 JDK 1.4 引入的 NIO 提 供了基于 Channel 與 Buffer 的 IO 方式, 它可以使用 Native 函數庫直接分配堆外內存, 然后使用 DirectByteBuffer 對象作為這塊內存的引用進行操作(詳見: Java I/O 擴展), 這樣就避免了在 Java 堆和 Native 堆中來回復制數據, 因此在一些場景中可以顯著提高性能。
2.2.1. 程序計數器(線程私有)
一塊較小的內存空間, 是當前線程所執行的字節碼的行號指示器,每條線程都要有一個獨立的 程序計數器,這類內存也稱為“線程私有”的內存。 正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。如 果還是 Native 方法,則為空。 這個內存區域是唯一一個在虛擬機中沒有規定任何 OutOfMemoryError 情況的區域。
2.2.2. 虛擬機棧(線程私有)
是描述java方法執行的內存模型,每個方法在執行的同時都會創建一個棧幀(Stack Frame) 用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。每一個方法從調用直至執行完成 的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。 棧幀( Frame)是用來存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接 (Dynamic Linking)、 方法返回值和異常分派( Dispatch Exception)。棧幀隨著方法調用而創建,隨著方法結束而銷毀——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異 常)都算作方法結束。
2.2.3. 本地方法區(線程私有)
本地方法區和 Java Stack 作用類似, 區別是虛擬機棧為執行 Java 方法服務, 而本地方法棧則為 Native 方法服務, 如果一個 VM 實現使用 C-linkage 模型來支持 Native 調用, 那么該棧將會是一個 C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機棧合二為一。
2.2.4. 堆(Heap-線程共享)-運行時數據區
是被線程共享的一塊內存區域,創建的對象和數組都保存在 Java 堆內存中,也是垃圾收集器進行 垃圾收集的最重要的內存區域。由于現代 VM 采用分代收集算法, 因此 Java 堆從 GC 的角度還可以 細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年代。
2.2.5. 方法區/永久代(線程共享)
即我們常說的永久代(Permanent Generation), 用于存儲被 JVM 加載的類信息、常量、靜 態變量、即時編譯器編譯后的代碼等數據. HotSpot VM把GC分代收集擴展至方法區, 即使用Java 堆的永久代來實現方法區, 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分內存, 而不必為方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型 的卸載, 因此收益一般很小)。 運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版 本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內容將在類加 載后存放到方法區的運行時常量池中。 Java 虛擬機對 Class 文件的每一部分(自然也包括常量 池)的格式都有嚴格的規定,每一個字節用于存儲哪種數據都必須符合規范上的要求,這樣才會 被虛擬機認可、裝載和執行。
2.3.JVM 運行時內存
Java 堆從 GC 的角度還可以細分為: 新生代(Eden 區、From Survivor 區和 To Survivor 區)和老年 代。
2.3.1. 新生代
是用來存放新生的對象。一般占據堆的 1/3 空間。由于頻繁創建對象,所以新生代會頻繁觸發 MinorGC 進行垃圾回收。新生代又分為 Eden 區、ServivorFrom、ServivorTo 三個區。
2.3.1.1. Eden 區
Java 新對象的出生地(如果新創建的對象占用內存很大,則直接分配到老 年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行 一次垃圾回收。
2.3.1.2. ServivorFrom
上一次 GC 的幸存者,作為這一次 GC 的被掃描者。
2.3.1.3. ServivorTo
保留了一次 MinorGC 過程中的幸存者。
2.3.1.4. 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 區。
2.3.2. 老年代
主要存放應用程序中生命周期長的內存對象。 老年代的對象比較穩定,所以 MajorGC 不會頻繁執行。在進行 MajorGC 前一般都先進行 了一次 MinorGC,使得有新生代的對象晉身入老年代,導致空間不夠用時才觸發。當無法找到足 夠大的連續空間分配給新創建的較大對象時也會提前觸發一次 MajorGC 進行垃圾回收騰出空間。 MajorGC 采用標記清除算法:首先掃描一次所有老年代,標記出存活的對象,然后回收沒 有標記的對象。MajorGC 的耗時比較長,因為要掃描再回收。MajorGC 會產生內存碎片,為了減 少內存損耗,我們一般需要進行合并或者標記出來方便下次直接分配。當老年代也滿了裝不下的 時候,就會拋出 OOM(Out of Memory)異常。
2.3.3. 永久代
指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被 放入永久區域,它和和存放實例的區域不同,GC 不會在主程序運行期對永久區域進行清理。所以這 也導致了永久代的區域會隨著加載的 Class 的增多而脹滿,最終拋出 OOM 異常。
2.3.3.1. JAVA8 與元數據
在 Java8 中,永久代已經被移除,被一個稱為“元數據區”(元空間)的區域所取代。元空間 的本質和永久代類似,元空間與永久代之間最大的區別在于:元空間并不在虛擬機中,而是使用 本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據放入 native memory, 字符串池和類的靜態變量放入 java 堆中,這樣可以加載多少類的元數據就不再由 MaxPermSize 控制, 而由系統的實際可用空間來控制。
未完待續……
加VX:13272413561,提前一飽眼福
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。