天天看點

一文就帶你把JVM并發标記清除回收,中繼資料記憶體管理給一次性搞懂

作者:大資料架構師

中繼資料記憶體管理

從JDK 8開始,中繼資料從堆空間中被移除,放入本地記憶體中。為了更好地管理中繼資料空間,JVM也設計了一套獨立的記憶體配置設定和回收的實作。在JDK 16之前的實作中,中繼資料記憶體管理的底層實作也使用二叉樹的記憶體塊管理,使用的資料結構與CMS老生代中大塊自由空間的資料結構完全相同。是以本節介紹一下中繼資料記憶體的管理。

中繼資料區(也稱為Metaspace)是JVM中一塊非常重要的記憶體空間,應用運作時遇到中繼資料空間不足的情況會直接觸發Full GC,對性能會産生一定的影響。

記憶體管理

Metaspace和類加載資料(Class Loader Data,CLD)關聯,簡單地講,每一個CLD都有一個Metaspace,CLD加載的所有類産生的中繼資料都在其對應的Metaspace中管理。

整個JVM中所有的Metaspace使用的記憶體都通過VirtualSpaceList(簡稱VLS)管理,而VSL中的每一個節點(Node)使用VirtualSpaceNode(簡稱VSN)管理。使用VSN的目的是便于回收記憶體。資料整體結構圖如圖4-34所示。

一文就帶你把JVM并發标記清除回收,中繼資料記憶體管理給一次性搞懂

圖4-34 中繼資料空間記憶體管理整體結構

每一個Node(即VSN)和VirtualSpace關聯,VirtualSpace的記憶體使用總是按照順序從頭開始。但是類中繼資料的大小并不相同,導緻Metaspace管理的記憶體塊是多樣化的,在類不再使用時還可以被解除安裝,占用的空間可以被回收。為了更好地管理中繼資料的配置設定和回收,在VSN中引入了以下兩類結構:

1)固定大小的結構,稱為chunk。目前有3種chunk大小,分别是128B、512B和8KB。

2)二叉樹,管理超過8KB大小的記憶體塊。

VSN和VirtualSpace的關系如圖4-35所示。

一文就帶你把JVM并發标記清除回收,中繼資料記憶體管理給一次性搞懂

圖4-35 VirtualSpaceNode和VirtualSpace關系示意圖

在圖4-35中還有一個ChunkManager(簡稱CM)也是全局共享的,主要目的是當類被解除安裝時對應的Metaspace會被釋放。但可能其使用的VSN中還存儲其他的類中繼資料,是以VSN無法被回收,是以會将釋放的中繼資料記憶體放入ChunkManager中,供後續的中繼資料配置設定使用。

Metaspace中有一個成員變量SpaceManager,用于管理本Metaspace對應的CLD真正使用的記憶體塊(包括固定大小的記憶體塊和二叉樹中的記憶體塊)。在SpaceManager中還有一個BlockFreeList的連結清單,用于儲存一些零碎的記憶體(這些記憶體通常來自類加載失敗或者類因重定義被重新加載而釋放的記憶體)。

配置設定

Metaspace的記憶體配置設定過程大體可以分為如下幾步:

1)嘗試從BlockFreeList中進行配置設定,但是由于BlockFreeList對于小微記憶體直接使用連結清單方式管理,空間不連續,是以配置設定成本比較高。在JVM中會設定一定的條件,當滿足這些條件時才能從BlockFreeList中配置設定。

2)無法從BlockFreeList中配置設定時,從Metaspace中正在使用的chunk中配置設定,這個chunk相當于緩存,用于加速配置設定。

3)沒有可用的chunk時會配置設定一個chunk再響應配置設定。配置設定chunk的邏輯相當複雜:優先從ChunkManager中重用已經釋放的chunk,如果無法找到合适的chunk,則需要從VirtualSpaceNode中配置設定;如果VirtualSpaceNode也無法滿足配置設定需求,會擴充VirtualSpaceList的大小再來配置設定。

VirtualSpaceList的擴充是指為VirtualSpaceList配置設定新的VirtualSpaceNode。每個VirtualSpaceNode的大小為256KB(無法在運作時态調整其大小),将VirtualSpaceNode限制為256KB的目的是便于回收VirtualSpaceNode,當整個VirtualSpaceNode沒有任何chunk時就可以被回收。

回收

了解了Metaspace的配置設定以後,其回收過程就不難了解了。由于Metaspace和CLD相關聯,當CLD被解除安裝以後就可以執行Metaspace的回收。

Metaspace的回收首先是将解除安裝的類中繼資料記憶體塊歸還到ChunkManager中,以便後續再利用。在這一步中并不會真正釋放記憶體,僅僅是将記憶體歸還到ChunkManager中,以便其再次被利用。而Metaspace真正的回收是針對VirtualSpaceNode的回收,當且僅當VirtualSpaceNode中所有的chunk都是空閑的才能被釋放,而這樣的情況并不常見。通常來說,Metaspace的回收僅僅是将類中繼資料占用的空間釋放再利用,很少能真正地回收記憶體。

在實際工作中可能會遇到Metaspace頻繁觸發Full GC的情況,通常有兩種可能:一是Metaspace空間太小,無法滿足應用的需要;二是Metaspace碎片化率非常高,導緻記憶體使用率不高。對于這兩種情況,目前并沒有特别好的解決方法,一方面要在應用中盡可能避免大量地、無限制地使用反射等消耗中繼資料空間的操作,另一方面可以考慮設定更大的中繼資料空間。

中繼資料管理的優化

在JDK 16中正式合入一個關于中繼資料的更新檔(patch),用于優化中繼資料的管理,詳細内容參考JEP 387[1]。這個特性本質上最大的改變是:

ChunkManager中chunk使用夥伴存儲來管理。夥伴存儲管理是一個非常經典的記憶體管理技術,它配置設定速度快,造成的記憶體碎片少。

夥伴存儲可以簡單地概括如下将記憶體塊大小按照2的幂次劃分,假設記憶體塊從上向下逐漸變小,上一層的記憶體塊的大小等于下一層兩個記憶體塊的大小,在下一層記憶體不足時可以從上一層擷取一個記憶體塊并拆分為下一層的兩個記憶體塊,當下一層兩個空閑的記憶體塊連續時可以合并到上一層變為一個記憶體塊。

中繼資料的chunk中共劃分為13層,最大的記憶體塊是第0層,為4MB,最小的記憶體塊是第12層,為1KB。每一層都是一個FreeList,管理相同大小的記憶體塊,如圖4-36所示。

一文就帶你把JVM并發标記清除回收,中繼資料記憶體管理給一次性搞懂

圖4-36 夥伴存儲示意圖

使用新的管理方式來配置設定中繼資料,通常根據記憶體需要的大小從期望的level的FreeList中配置設定記憶體。當無法滿足配置設定時,會從更大的記憶體塊中嘗試配置設定,這就涉及記憶體塊的拆分。在JDK 16的實作中,按照如下順序進行記憶體配置設定:

1)從level、level+1、level+2中依次請求配置設定記憶體,此時查找的是已經使用的記憶體塊。

2)如果不成功,從level0中依次請求配置設定記憶體,此時查找的是已經使用的記憶體塊。

3)如果不成功,再次嘗試,從level12中依次請求配置設定記憶體,此時查找的是已經使用的記憶體塊。

4)如果不成功,從level12中依次請求配置設定記憶體,此時查找的是是否存在完全未使用的記憶體塊。

5)如果不成功,從level0中依次請求配置設定記憶體,此時查找的是是否存在完全未使用的記憶體塊。

6)如果不成功,重新請求一個VirtualSpaceNode(大小為8MB),可以拆分成兩個第0層的記憶體塊,再次嘗試配置設定。

另外,通常情況下中繼資料的配置設定是從chunk中獲得的,但是在一些特殊的場景中,例如類加載失敗或者類因重定義被重新加載,可能會導緻需要釋放記憶體。将這些記憶體單獨管理起來并且重用,可以有效地提高記憶體使用的效率。使用FreeBlock的方式管理釋放的記憶體,在FreeBlock中采用BinList和BinTree管理釋放的記憶體。

本文給大家講解的内容是JVM垃圾回收器詳解:并發标記清除回收,中繼資料記憶體管理

  1. 下篇文章給大家講解的内容是JVM垃圾回收器詳解:并行回收的記憶體管理
  2. 感謝大家的支援!

繼續閱讀