JVM 內存模型和垃圾回收網絡摘要總結

有些知識平常基本不會用到的,當每次去找工作的時候,都會被面試被問到,我想大家都猜到了就是中高級Java程序員一定會問的JVM內存模型和gc算法。想下我每次回答都是模模糊糊,知識點片片段段的,做技術最怕就是這樣的,你說我不會吧,我的心又不甘,我確實懂,懂一點點點點🤦‍。看了很多別人寫的JVM博客,想寫一篇自己學習筆記心得,當我能寫出來了,這些知識不單止刻在我的腦子了,還會留在我的心裏。本文參考多篇博客內容編寫的,如有雷同肯定是抄襲了。

JVM 運行時內存

根據OracleJVM運行的結構的說明,Java虛擬機在定義了在程序執行期間使用的各種運行時數據區域。

  • 程序計數器: 記錄當前線程正在執行的Java虛擬機指令的地址,每個線程都有自己的程序計數器。
  • Java虛擬機堆棧: 每個Java虛擬機線程都有一個私有Java虛擬機堆棧,與該線程同時創建。它保存局部變量和部分結果,並在方法調用和返回中起作用。
  • 堆: Java虛擬機運行時產生所有的對象和數組的內存區域,所有線程共享的。
  • 方法區: 主要存儲每個類的信息包括類的版本、字段、方法、接口和常量池。儘管方法區域在邏輯上是堆的一部分,但是簡單的實現可以選擇不進行垃圾回收或壓縮
  • 運行時常量池: 就是方法區裏面常量池,主要是類或者接口的各種常量類型。
  • 本地方法區: 調用系統原生native方法,產生棧內存結構。

以上區域雖然都是單獨劃分,但是方法區運行時常量池都是在Heap內存中,當存儲不足的時候,則Java虛擬機機器拋出一個OutOfMemoryError。可以通過Java啓動參數修改默認Heap內存 -xms -xmx 設置最大最小內存。本地方法區屬於Java虛擬機堆棧,如果線程中的計算所需的Java虛擬機堆棧超出允許的範圍,則Java虛擬機將拋出StackOverflowError。可以通過Java啓動參數-xss調整堆棧內存大小。

JVM內存模型

  • Young Generation: 翻譯叫年輕代,主要有Eden SpaceSurvivor Space組成。
  • Eden Space: 所有新創建對象都是從這裏分配內存的。
  • Survivor Space: 倖存區空間,這裏包含的對象都是從年輕代垃圾回收或者 Minor gc中倖存下來的,垃圾回收下面會具體說明。
  • Tenured Space: 也叫Old Generation Space 老年代,年輕代的對象經過young gc 或者minor gc 達到最大幸存閾值將被移動到老年代。
  • Meta Space: 元空間,這個是堆外內存,使用本機原生內存。元空間可以獨立申請內存空間,不受JVM內存限制。元空間主要用於存儲由類加載器加載的類定義,在1.7以前稱作Perm Gen Space
  • Code Cache: JVM具有解釋器來解釋字節碼並將其轉換爲依賴於硬件的機器碼。作爲JVM優化的一部分,Just In Time(JIT)引入了編譯器,經常訪問的代碼塊將由JIT編譯爲本地代碼,並將其存儲在代碼緩存中。

回收算法

下面圖片轉載一文看懂 JVM 內存佈局及 GC 原理

mark-sweep 標記清除法

將需要回收對象全部標記出來直接清空,優點回收速度快,但是會產生很多內存碎片。

mark-copy 標記複製法


將內存分成大小相等的區域,將存活對象複製到新的區域,再將原來整個區域刪除,這種算法回收速度快,不會產生內存碎片,內存利用率只能用50%。這種回收算法用於Survivor Space,一個S0區域保存對象,另一個S1保持空閒,當執行標記複製時,倖存對象移動到空閒的S1,S0清除對象變成空閒區域。

mark-compact 標記 - 整理(也稱標記 - 壓縮)法

避免了上述兩種算法的缺點,將垃圾對象清理掉後,同時將剩下的存活對象進行整理挪動(類似於 windows 的磁盤碎片整理),保證它們佔用的空間連續,這樣就避免了內存碎片問題,但是整理過程也會降低 GC 的效率。這種算法主要用於老年代對象回收。

對象回收判斷

主要通過兩個方法去判斷對象滿足垃圾回收:對象引用計數,對象可達性分析。

  • 對象引用計算 : 給對象添加一個引用計數器,當對象被引用時,計數器+1,引用失效時計數器-1,計數器爲0就滿足垃圾回收。但是這種方法不能處理循環引用的對象,比如一個對象A引變了對象B,對象B引用了對象A。這樣會導致計數器永遠不會爲0,這時就要使用對象可達性分析來判斷了。
  • 對象可達性分析: 通過GC Root作爲起點向下遍歷,走過的路徑作爲引用鏈,以引用鏈上的對象作爲可達性對象。

    當A、B對象沒有被GC ROOT所引用,對象是不可達的,會被垃圾回收器清除掉的。

垃圾回收過程

  • Minor gc: 從年輕代(包括Eden和倖存區)進行垃圾回收的統稱。
  • Major gc: 回收"Turn Space"
  • Full gc: 清理整個堆空間包括"Young space"和"Turn Space"一起回收。

    如圖所有新創建的對象都保存在Eden Space,當空間快滿時,JVM就是啓動minor gc回收不可達的對象。JVM選擇倖存者空間之一作爲“ To Space”,比如將S0當作"To Space"。JVM將可訪問對象複製到" To Space"(S0),並將可訪問對象的年齡增加1。當有大對象生成時,不適合進入倖存區而直接移動到年老代。

    在上圖中,紅色的對象是將被垃圾回收的對象,倖存對象對象複製到 "To Space",清空"Eden Space"空間。

    第二次minor gc,"Eden Space"、"To survivor space (S0)"的不可達對象都會被回收,複製倖存對象到S1倖存區中,倖存對象年齡將自增+1,在清空"Eden Space"和"To survivor space (S0)"。

    沒經過一個minor gc,存活下來的對象年齡就會增加1,當對象那年齡達到最大閾值15,對象將會移動到年老代,可通過-XX:MaxTenuringThreshold調整這個閾值。根據對象年齡有另外一個策略也會讓對象進入老年代,不用等待15次GC之後進入老年代,他的大致規則就是,假如當前放對象的Survivor,一批對象的總大小大於這塊Survivor內存的50%,那麼大於這批對象年齡的對象,就可以直接進入老年代了。

總結那些情況會觸發Major gc:

  1. 開發者調用System.gc()或者Runtime.getRunTime().gc()JVM啓動GC。
  2. 年老代空間不足
  3. 在Minor gc期間,如果JVM無法從伊甸園或倖存者空間中回收足夠的內存,則可能會觸發Major GC。
  4. 如果我們爲JVM設置了“ MaxMetaspaceSize”選項,但沒有足夠的空間來加載新類,則JVM會觸發一個Major GC。

參考資料
https://www.infoq.cn/article/3WyReTKqrHIvtw4frmr3
https://dzone.com/articles/understanding-the-java-memory-model-and-the-garbag
https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc

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