JVM源碼分析之Metaspace解密

本文來自: PerfMa技術社區

PerfMa(笨馬網絡)官網javascript

概述

metaspace,顧名思義,元數據空間,專門用來存元數據的,它是jdk8裏特有的數據結構用來替代perm,這塊空間頗有本身的特色,前段時間公司這塊的問題太多了,主要是由於升級了中間件所致,看到你們討論來討論去,看得出不少人對metaspace仍是模棱兩可,不是很瞭解它,所以我以爲有必要寫篇文章來介紹一下它,解開它神祕的面紗,當咱們再次碰到它的相關問題的時候不會再感到一籌莫展。java

經過這篇文章,你將能夠了解到bootstrap

  • 爲何會有metaspace
  • metaspace的組成
  • metaspace的VM參數
  • jstat裏咱們應該關注metaspace的哪些值

爲何會有metaspace

metaspace的由來民間已有不少傳說,不過我這裏只談我本身的理解,由於我不是oracle參與這塊的開發者,因此對其真正的由來不怎麼了解。網絡

咱們都知道jdk8以前有perm這一整塊內存來存klass等信息,咱們的參數裏也必不可少地會配置-XX:PermSize以及-XX:MaxPermSize來控制這塊內存的大小,jvm在啓動的時候會根據這些配置來分配一塊連續的內存塊,可是隨着動態類加載的狀況愈來愈多,這塊內存咱們變得不太可控,到底設置多大合適是每一個開發者要考慮的問題,若是設置過小了,系統運行過程當中就容易出現內存溢出,設置大了又總感受浪費,儘管不會實質分配這麼大的物理內存。基於這麼一個可能的緣由,因而metaspace出現了,但願內存的管理再也不受到限制,也不要怎麼關注元數據這塊的OOM問題,雖然到目前來看,也並無完美地解決這個問題。數據結構

或許從JVM代碼裏也能看出一些端倪來,好比MaxMetaspaceSize默認值很大,CompressedClassSpaceSize默認也有1G,從這些參數咱們能猜到metaspace的做者不但願出現它相關的OOM問題。oracle

metaspace的組成

metaspace其實由兩大部分組成jvm

  • Klass Metaspace
  • NoKlass Metaspace

Klass Metaspace就是用來存klass的,klass是咱們熟知的class文件在jvm裏的運行時數據結構,不過有點要提的是咱們看到的相似A.class實際上是存在heap裏的,是java.lang.Class的一個對象實例。這塊內存是緊接着Heap的,和咱們以前的perm同樣,這塊內存大小可經過-XX:CompressedClassSpaceSize參數來控制,這個參數前面提到了默認是1G,可是這塊內存也能夠沒有,假如沒有開啓壓縮指針就不會有這塊內存,這種狀況下klass都會存在NoKlass Metaspace裏,另外若是咱們把-Xmx設置大於32G的話,其實也是沒有這塊內存的,由於會這麼大內存會關閉壓縮指針開關。還有就是這塊內存最多隻會存在一塊。工具

NoKlass Metaspace專門來存klass相關的其餘的內容,好比method,constantPool等,這塊內存是由多塊內存組合起來的,因此能夠認爲是不連續的內存塊組成的。這塊內存是必須的,雖然叫作NoKlass Metaspace,可是也其實能夠存klass的內容,上面已經提到了對應場景。學習

Klass Metaspace和NoKlass Mestaspace都是全部classloader共享的,因此類加載器們要分配內存,可是每一個類加載器都有一個SpaceManager,來管理屬於這個類加載的內存小塊。若是Klass Metaspace用完了,那就會OOM了,不過通常狀況下不會,NoKlass Mestaspace是由一塊塊內存慢慢組合起來的,在沒有達到限制條件的狀況下,會不斷加長這條鏈,讓它能夠持續工做。spa

metaspace的幾個參數

若是咱們要改變metaspace的一些行爲,咱們通常會對其相關的一些參數作調整,由於metaspace的參數自己不是不少,因此我這裏將涉及到的全部參數都作一個介紹,也許好些參數你們都是有誤解的

  • UseLargePagesInMetaspace
  • InitialBootClassLoaderMetaspaceSize
  • MetaspaceSize
  • MaxMetaspaceSize
  • CompressedClassSpaceSize
  • MinMetaspaceExpansion
  • MaxMetaspaceExpansion
  • MinMetaspaceFreeRatio
  • MaxMetaspaceFreeRatio

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的大小。

MetaspaceSize

默認20.8M左右(x86下開啓c2模式),主要是控制metaspaceGC發生的初始閾值,也是最小閾值,可是觸發metaspaceGC的閾值是不斷變化的,與之對比的主要是指Klass Metaspace與NoKlass Metaspace兩塊committed的內存和。

MaxMetaspaceSize

默認基本是無窮大,可是我仍是建議你們設置這個參數,由於極可能會由於沒有限制而致使metaspace被無止境使用(通常是內存泄漏)而被OS Kill。這個參數會限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的內存大小,會保證committed的內存不會超過這個值,一旦超過就會觸發GC,這裏要注意和MaxPermSize的區別,MaxMetaspaceSize並不會在jvm啓動的時候分配一塊這麼大的內存出來,而MaxPermSize是會分配一塊這麼大的內存的。

CompressedClassSpaceSize

默認1G,這個參數主要是設置Klass Metaspace的大小,不過這個參數設置了也不必定起做用,前提是能開啓壓縮指針,假如-Xmx超過了32G,壓縮指針是開啓不來的。若是有Klass Metaspace,那這塊內存是和Heap連着的。

MinMetaspaceExpansion

MinMetaspaceExpansion和MaxMetaspaceExpansion這兩個參數或許和你們認識的並不同,也許不少人會認爲這兩個參數不就是內存不夠的時候,而後擴容的最小大小嗎?其實否則

這兩個參數和擴容其實並無直接的關係,也就是並非爲了增大committed的內存,而是爲了增大觸發metaspace GC的閾值

這兩個參數主要是在比較特殊的場景下救急使用,好比gcLocker或者should_concurrent_collect的一些場景,由於這些場景下接下來會作一次GC,相信在接下來的GC中可能會釋放一些metaspace的內存,因而先臨時擴大下metaspace觸發GC的閾值,而有些內存分配失敗其實正好是由於這個閾值觸頂致使的,因而能夠經過增大閾值暫時繞過去

默認332.8K,增大觸發metaspace GC閾值的最小要求。假如咱們要救急分配的內存很小,沒有達到MinMetaspaceExpansion,可是咱們會將此次觸發metaspace GC的閾值提高MinMetaspaceExpansion,之因此要大於此次要分配的內存大小主要是爲了防止別的線程也有相似的請求而頻繁觸發相關的操做,不過若是要分配的內存超過了MaxMetaspaceExpansion,那MinMetaspaceExpansion將會是要分配的內存大小基礎上的一個增量。

MaxMetaspaceExpansion

默認5.2M,增大觸發metaspace GC閾值的最大要求。假如說咱們要分配的內存超過了MinMetaspaceExpansion可是低於MaxMetaspaceExpansion,那增量是MaxMetaspaceExpansion,若是超過了MaxMetaspaceExpansion,那增量是MinMetaspaceExpansion加上要分配的內存大小

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

MinMetaspaceFreeRatio

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

默認40,表示每次GC完以後,假設咱們容許接下來metaspace能夠繼續被commit的內存佔到了被commit以後總共committed的內存量的MinMetaspaceFreeRatio%,若是這個總共被committed的量比當前觸發metaspaceGC的閾值要大,那麼將嘗試作擴容,也就是增大觸發metaspaceGC的閾值,不過這個增量至少是MinMetaspaceExpansion纔會作,否則不會增長這個閾值

這個參數主要是爲了不觸發metaspaceGC的閾值和gc以後committed的內存的量比較接近,因而將這個閾值進行擴大

通常狀況下在gc完以後,若是被committed的量仍是比較大的時候,換個說法就是離觸發metaspaceGC的閾值比較接近的時候,這個調整會比較明顯

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

MaxMetaspaceFreeRatio

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

jstat裏的metaspace字段

咱們看GC是否異常,除了經過GC日誌來作分析以外,咱們還能夠經過jstat這樣的工具展現的數據來分析。

咱們經過jstat能夠看到metaspace相關的這麼一些指標,分別是MCCSMCMUCCSCCCSUMCMNMCMXCCSMNCCSMX

它們的定義以下:

column {
    header "^M^"  /* Metaspace - Percent Used */
    data (1-((sun.gc.metaspace.capacity - sun.gc.metaspace.used)/sun.gc.metaspace.capacity)) * 100
    align right
    width 6
    scale raw
    format "0.00"
  }
  column {
    header "^CCS^"    /* Compressed Class Space - Percent Used */
    data (1-((sun.gc.compressedclassspace.capacity - sun.gc.compressedclassspace.used)/sun.gc.compressedclassspace.capacity)) * 100
    align right
    width 6
    scale raw
    format "0.00"
  }

  column {
    header "^MC^" /* Metaspace Capacity - Current */
    data sun.gc.metaspace.capacity
    align center
    width 6
    scale K
    format "0.0"
  }
  column {
    header "^MU^" /* Metaspae Used */
    data sun.gc.metaspace.used
    align center
    width 6
    scale K
    format "0.0"
  }
   column {
    header "^CCSC^"   /* Compressed Class Space Capacity - Current */
    data sun.gc.compressedclassspace.capacity
    width 8
    align right
    scale K
    format "0.0"
  }
  column {
    header "^CCSU^"   /* Compressed Class Space Used */
    data sun.gc.compressedclassspace.used
    width 8
    align right
    scale K
    format "0.0"
  }
  column {
    header "^MCMN^"   /* Metaspace Capacity - Minimum */
    data sun.gc.metaspace.minCapacity
    scale K
    align right
    width 8
    format "0.0"
  }
  column {
    header "^MCMX^"   /* Metaspace Capacity - Maximum */
    data sun.gc.metaspace.maxCapacity
    scale K
    align right
    width 8
    format "0.0"
  }
  column {
    header "^CCSMN^"    /* Compressed Class Space Capacity - Minimum */
    data sun.gc.compressedclassspace.minCapacity
    scale K
    align right
    width 8
    format "0.0"
  }
  column {
    header "^CCSMX^"  /* Compressed Class Space Capacity - Maximum */
    data sun.gc.compressedclassspace.maxCapacity
    scale K
    align right
    width 8
    format "0.0"
  }

我這裏對這些字段分類介紹下

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二者總共的使用率
  • 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的內存大小

綜上所述,其實看metaspace最主要的仍是看MC,MU,CCSC,CCSU這幾個具體的大小來判斷metaspace到底用了多少更靠譜
原本還想寫metaspace內存分配和GC的內容,不過那塊提及來又是一個比較大的話題,由於那塊你們看起來可能會比較枯燥,有機會再寫

一塊兒來學習吧

PerfMa KO 系列課之 JVM 參數【Memory篇】

YGC問題排查,又讓我漲姿式了!

相關文章
相關標籤/搜索