JVM內存模型、GC垃圾回收等

問題:今天讓我們一起進入JVM比價深層一點的領域——JVM內存模型(包括GC回收)


一、jvm基本介紹

1、JVM 是可運行 Java 代碼的假想計算機 ,包括一套字節碼指令集、一組寄存器、一個棧、 一個垃圾回收,堆 和 一個存儲方法域。JVM 是運行在操作系統之上的,它與硬件沒有直接 的交互,下面是它的基本結構圖

2、運行過程:

① Java 源文件—->編譯器—->字節碼文件

② 字節碼文件—->JVM—->機器碼

補:每一種平臺的解釋器是不同的,但是實現的虛擬機是相同的,這也就是 Java 爲什麼能夠 跨平臺的原因了 ,當一個程序從開始運行,這時虛擬機就開始實例化了,多個程序啓動就會 存在多個虛擬機實例。程序退出或者關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不 能共享。

3、線程:

這裏所說的線程指程序執行過程中的一個線程實體。JVM 允許一個應用併發執行多個線程。 Hotspot JVM 中的 Java 線程與原生操作系統線程有直接的映射關係。當線程本地存儲、緩 衝區分配、同步對象、棧、程序計數器等準備好以後,就會創建一個操作系統原生線程。 Java 線程結束,原生線程隨之被回收。操作系統負責調度所有線程,並把它們分配到任何可 用的 CPU 上。當原生線程初始化完畢,就會調用 Java 線程的 run() 方法。當線程結束時,會釋放原生線程和 Java 線程的所有資源。

補:Hotspot JVM 後臺運行的系統線程主要有下面幾個:

 


二、jvm內存模型

 

2.1、JVM 內存區域主要分:

  • 線程私有區域【程序計數器、虛擬機棧、本地方法區】
  • 線程共享區 域【JAVA 堆、方法區】、直接內存。

2.1.1、程序計數器(線程私有)

  • 它是佔用最小的一塊內存,是當前線程執行的指令指示器,也是唯一的不會出現OOM的內存區域
  • 正在執行 java 方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。若是 Native 方法,則爲空。

2.1.2、虛擬機棧(線程私有)

  • 存放我們的局部變量、返回地址、動態鏈接之類等信息,還有一個方法的執行過程,每一個方法從調用直至執行完成 的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。
  • 棧幀是用來存放數據和部分過程結果的數據結構,還可以用作動態鏈接處理,以及異常報錯處理,方法返回值處理
  • 隨着方法結束而銷燬——無論方法是正常完成還是異常完成(拋出了在方法內未被捕獲的異 常)都算作方法結束。

2.1.3、本地方法區(線程私有)

  • 本地方法區和 Java Stack 作用類似, 區別是虛擬機棧爲執行 Java 方法服務, 而本地方法棧則爲 Native 方法服務, 如果一個 VM 實現使用 C-linkage 模型來支持 Native 調用, 那麼該棧將會是一個 C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機棧合二爲一。

2.1.4、堆(Heap-線程共享)-運行時數據區

  • 堆是一個共享內存區域,用來存放數組和對象
  • 堆是gc回收的主要內存區域,同時如果按照gc劃分的話,其實堆又可以分爲
    • 新生代(Eden區域、Survivor From、Survivor To 三個區)
    • 老生代

2.1.5、方法區/永久代(線程共享)

  • 用於存儲被 JVM 加載的類信息、常量、靜 態變量、即時編譯器編譯後的代碼等數據.
  • HotSpot VM把GC分代收集擴展至方法區, 即使用Java 堆的永久代來實現方法區, 這樣 HotSpot 的垃圾收集器就可以像管理 Java 堆一樣管理這部分內存, 而不必爲方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型 的卸載, 因此收益一般很小)。

補:運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版 本、字段、方法、接口等描述等信息外,還有一項信息是常量池 13/04/2018 Page 24 of 283 (Constant Pool Table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加 載後存放到方法區的運行時常量池中。 Java 虛擬機對 Class 文件的每一部分(自然也包括常量 池)的格式都有嚴格的規定,每一個字節用於存儲哪種數據都必須符合規範上的要求,這樣纔會 被虛擬機認可、裝載和執行。


2.2、JVM 運行時【堆】內存劃分(按照gc劃分重新劃分【堆】內存)

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

2.2.1、新生代 Eden 區

Java 新對象的出生地(如果新創建的對象佔用內存很大,則直接分配到老 年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行 一次垃圾回收。

MinorGC採用複製算法、過程如下(複製->清空->互換):

  • eden、servicorFrom 複製到 ServicorTo,年齡+1 首先,把 Eden 和 ServivorFrom 區域中存活的對象複製到 ServicorTo 區域(如果有對象的年 齡以及達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1(如果 ServicorTo 不 夠位置了就放到老年區)
  • 清空 eden、servicorFrom 然後,清空 Eden 和 ServicorFrom 中的對象
  • ServicorTo 和 ServicorFrom 互換 最後,ServicorTo 和 ServicorFrom 互換,原 ServicorTo 成爲下一次 GC 時的 ServicorFrom 區

2.2.2、新生代 From Survivor 區

  • 上一次 GC 的倖存者,作爲這一次 GC 的被掃描者。

2.2.3、新生代 To Survivor 區

  • 保留了一次 MinorGC 過程中的倖存者。

2.2.4、老年 代

  • 主要存放應用程序中生命週期長的內存對象。
  • 是比較穩定的一些對象,會執行 MajorGC 回收,但是之前會進行一次 MinorGC 回收,導致空間不夠用纔會觸發,還有如果新創建的對象需要很大的空間佔用,但是發現連續分配的空間不夠用時也會觸發,給這個對象騰出內存空間
  • 採用標記清除法進行回收

標記清除法:首先先掃描所有的老年代的對象,標記處還存活的對象,然後清除沒有被標記的對象,進行Major回收時間很長,因爲需要掃描所有的老年代對象,然後在回收,同時也會會產生內存碎片,爲了減少這個內存碎片的產生,我們可以對其進行合併或者是標記出來,下次可以直接分配使用,如果老年代都不夠用了,那就會爆出OOM異常了

2.2.5. 永久代

  • 指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被 放入永久區域,它和和存放實例的區域不同,GC 不會在主程序運行期對永久區域進行清理。所以這 也導致了永久代的區域會隨着加載的 Class 的增多而脹滿,最終拋出 OOM 異常。

JAVA8 與元數據:

在 Java8 中,永久代已經被移除,被一個稱爲“元數據區”(元空間)的區域所取代。元空間 的本質和永久代類似,元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用 本地內存。因此,默認情況下,元空間的大小僅受本地內存限制。類的元數據放入 native memory, 字符串池和類的靜態變量放入 java 堆中,這樣可以加載多少類的元數據就不再由 MaxPermSize 控制, 而由系統的實際可用空間來控制。

☛同行們,以上僅是自己結合資料文檔整理的,希望對你會有所幫助吧,如有誤或需要補充歡迎留言評論,謝謝!

☛下一篇GC回收因內容太長,所以需要到下一篇文章嘍查看GC垃圾回收

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