JVM——元空間(Metaspace)

一、從方法區(PermGen)到元空間(Metaspace)

  • 方法區(PermGen)
  1. JDK1.8以前的HotSpot JVM有方法區,也叫永久代(permanent generation)
  2. 方法區用於存放已被虛擬機加載的類信息、常量、靜態變量,即編譯器編譯後的代碼
  3. 方法區是一片連續的堆空間,通過-XX:MaxPermSize來設定永久代最大可分配空間,當JVM加載的類信息容量超過了這個值,會報OOM:PermGen錯誤。
  4. 永久代的GC是和老年代(old generation)捆綁在一起的,無論誰滿了,都會觸發永久代和老年代的垃圾收集。
  5. JDK1.7開始了方法區的部分移除:符號引用(Symbols)移至native heap字面量(interned strings)靜態變量(class statics)移至java heap。
  • 爲什麼要用Metaspace替代方法區
    隨着動態類加載的情況越來越多,這塊內存變得不太可控,如果設置小了,系統運行過程中就容易出現內存溢出,設置大了又浪費內存。

二、Metaspace的組成

Metaspace由兩大部分組成:Klass Metaspace和NoKlass Metaspace。

  • Klass Metaspace
  1. Klass Metaspace就是用來存klass的,就是class文件在jvm裏的運行時數據結構(不過我們看到的類似A.class其實是存在heap裏的,是java.lang.Class的對象實例)。
  2. 這部分默認放在Compressed Class Pointer Space中,是一塊連續的內存區域,
    緊接着Heap,和之前的perm一樣。通過-XX:CompressedClassSpaceSize來控制這塊內存的大小,默認是1G。
    下圖展示了對象的存儲模型,_mark是對象的Mark Word,_klass是元數據指針

    has ccs.jpg

  3. Compressed Class Pointer Space不是必須有的,如果設置了-XX:-UseCompressedClassPointers,或者-Xmx設置大於32G,就不會有這塊內存,這種情況下klass都會存在NoKlass Metaspace裏。

    no ccs.jpg

  • NoKlass Metaspace
  1. NoKlass Metaspace專門來存klass相關的其他的內容,比如method,constantPool等,可以由多塊不連續的內存組成。
  2. 這塊內存是必須的,雖然叫做NoKlass Metaspace,但是也其實可以存klass的內容,上面已經提到了對應場景。
  3. NoKlass Metaspace在本地內存中分配。
  • 指針壓縮
  1. 64位平臺上默認打開
  2. 設置-XX:+UseCompressedOops壓縮對象指針, oops指的是普通對象指針(ordinary object pointers), 會被壓縮成32位。
  3. 設置-XX:+UseCompressedClassPointers壓縮類指針,會被壓縮成32位。

三、Metaspace內存管理

  1. 在metaspace中,類和其元數據的生命週期與其對應的類加載器相同,只要類的類加載器是存活的,在Metaspace中的類元數據也是存活的,不能被回收。
  2. 每個加載器有單獨的存儲空間。
  3. 省掉了GC掃描及壓縮的時間。
  4. 當GC發現某個類加載器不再存活了,會把對應的空間整個回收。

Metaspace VM使用一個塊分配器(chunking allocator)來管理Metaspace空間的內存分配。塊的大小依賴於類加載器的類型。
Metaspace VM中有一個全局的可使用的塊列表(a global free list of chunks)。當類加載器需要一個塊的時候,類加載器從全局塊列表中取出一個塊,添加到它自己維護的塊列表中。當類加載器死亡,它的塊將會被釋放,歸還給全局的塊列表。
塊(chunk)會進一步被劃分成blocks,每個block存儲一個元數據單元(a unit of metadata)。Chunk中Blocks的是分配線性的(pointer bump)。這些chunks被分配在內存映射空間(memory mapped(mmapped) spaces)之外。在一個全局的虛擬內存映射空間(global virtual mmapped spaces)的鏈表,當任何虛擬空間變爲空時,就將該虛擬空間歸還回操作系統。

metachunks.jpg


上面這幅圖展示了Metaspace使用metachunks在mmapeded virual spaces分配的情形。

 

四、metaspace的主要參數

MetaspaceSize
MaxMetaspaceSize
CompressedClassSpaceSize
MinMetaspaceExpansion
MaxMetaspaceExpansion
MinMetaspaceFreeRatio
MaxMetaspaceFreeRatio
UseLargePagesInMetaspace
InitialBootClassLoaderMetaspaceSize

MetaspaceSize

metaspaceGC發生的初始閾值,也是最小閾值,默認20.8M左右,與之對比的主要是指Klass Metaspace與NoKlass Metaspace兩塊committed的內存和。

  • 觸發metaspaceGC的閾值是不斷變化的:當metaspace使用的內存接近閾值時,會嘗試增大閾值。metaspaceGC後也會調整閾值。

MaxMetaspaceSize

由於metaspace大部分在本地內存中分配,默認基本是無窮大,但仍然受本地內存大小的限制。爲了防止metaspace被無止境使用,建議設置這個參數。

  • 這個參數會限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的內存大小,會保證committed的內存不會超過這個值,一旦超過就會觸發GC。
  • 和MaxPermSize的區別,根據MaxMetaspaceSize,並不會在jvm啓動的時候分配一塊這麼大的內存出來,而根據MaxPermSize分配的內存則是固定大小。

CompressedClassSpaceSize

Compressed Class Pointer Space區域的大小,默認1G。
如果設置了-XX:-UseCompressedClassPointers,或者-Xmx設置大於32G,則這個參數不生效。

MinMetaspaceExpansion

MinMetaspaceExpansion和MaxMetaspaceExpansion這兩個參數這兩個參數和擴容其實並沒有直接的關係,並不是爲了增大committed的內存。
主要是在比較特殊的場景下救急使用,增大觸發metaspace GC的閾值,延遲GC的發生。
默認332.8K,增大觸發metaspace GC閾值的最小要求。
如果需要分配的內存小於MinMetaspaceExpansion,則將metaspace GC的閾值提升MinMetaspaceExpansion。

MaxMetaspaceExpansion

默認5.2M,增大觸發metaspace GC閾值的最大要求。
如果需要分配的內存大於MinMetaspaceExpansion但是小於MaxMetaspaceExpansion,那增量就是MaxMetaspaceExpansion。
如果需要分配的內存超過了MaxMetaspaceExpansion,那增量就是MinMetaspaceExpansion加上要分配的內存大小

注:每次分配只會給對應的線程一次擴展觸發metaspace GC閾值的機會,如果擴展了,但是還不能分配,那就只能等着做GC了。

MinMetaspaceFreeRatio

MinMetaspaceFreeRatio和下面的MaxMetaspaceFreeRatio,主要是影響觸發metaspaceGC的閾值。

默認40,表示每次GC完之後,如果metaspace內存的空閒比例小於MinMetaspaceFreeRatio%,那麼將嘗試做擴容,增大觸發metaspaceGC的閾值。
不過這個增量至少是MinMetaspaceExpansion纔會做,不然不會增加這個閾值。
這個參數主要是爲了避免觸發metaspaceGC的閾值和gc之後committed的內存的量比較接近,於是將這個閾值進行擴大。

注:這裏不用gc之後used的量來算,主要是擔心可能出現committed的量超過了觸發metaspaceGC的閾值,這種情況一旦發生會很危險,會不斷做gc,這應該是jdk8在某個版本之後才修復的bug

MaxMetaspaceFreeRatio

默認70,這個參數和上面的參數基本是相反的,是爲了避免觸發metaspaceGC的閾值過大,而想對這個值進行縮小。
這個參數在gc之後committed的內存比較小的時候並且離觸發metaspaceGC的閾值比較遠的時候,調整會比較明顯。

UseLargePagesInMetaspace

默認false,這個參數是說是否在metaspace裏使用LargePage,一般情況下我們使用4KB的page size,這個參數依賴於UseLargePages這個參數開啓,不過這個參數我們一般不開。

InitialBootClassLoaderMetaspaceSize

64位下默認4M,32位下默認2200K,metasapce前面已經提到主要分了兩大塊,Klass Metaspace以及NoKlass Metaspace,而NoKlass Metaspace是由一塊塊內存組合起來的,這個參數決定了NoKlass Metaspace的第一個內存Block的大小,即2*InitialBootClassLoaderMetaspaceSize,同時爲bootstrapClassLoader的第一塊內存chunk分配了InitialBootClassLoaderMetaspaceSize的大小

五、jstat裏的metaspace字段

我們看GC是否異常,除了通過GC日誌來做分析之外,我們還可以通過jstat這樣的工具展示的數據來分析,前面我公衆號裏有篇文章介紹了jstat這塊的實現,有興趣的可以到我的公衆號你假笨裏去翻閱下jstat的這篇文章。

我們通過jstat可以看到metaspace相關的這麼一些指標,分別是M,CCS,MC,MU,CCSC,CCSU,MCMN,MCMX,CCSMN,CCSMX

MC & MU & CCSC & CCSU

  • MC表示Klass Metaspace以及NoKlass Metaspace兩者總共committed的內存大小,單位是KB,雖然從上面的定義裏我們看到了是capacity,但是實質上計算的時候並不是capacity,而是committed,這個是要注意的
  • MU這個無可厚非,說的就是Klass Metaspace以及NoKlass Metaspace兩者已經使用了的內存大小
  • CCSC表示的是Klass Metaspace的已經被commit的內存大小,單位也是KB
  • CCSU表示Klass Metaspace的已經被使用的內存大小

M & CCS

  • M表示的是Klass Metaspace以及NoKlass Metaspace兩者總共的使用率,其實可以根據上面的四個指標算出來,即(CCSU+MU)/(CCSC+MC)
  • CCS表示的是NoKlass Metaspace的使用率,也就是CCSU/CCSC算出來的

PS:所以我們有時候看到M的值達到了90%以上,其實這個並不一定說明metaspace用了很多了,因爲內存是慢慢commit的,所以我們的分母是慢慢變大的,不過當我們committed到一定量的時候就不會再增長了

MCMN & MCMX & CCSMN & CCSMX

  • MCMN和CCSMN這兩個值可以忽略,一直都是0
  • MCMX表示Klass Metaspace以及NoKlass Metaspace兩者總共的reserved的內存大小,比如默認情況下Klass Metaspace是通過CompressedClassSpaceSize這個參數來reserved 1G的內存,NoKlass Metaspace默認reserved的內存大小是2* InitialBootClassLoaderMetaspaceSize
  • CCSMX表示Klass Metaspace reserved的內存大小
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章