1. 程式人生 > >Metaspace整體介紹

Metaspace整體介紹

(永久代被替換原因、元空間特點、元空間記憶體檢視分析方法)

回顧

根據JVM記憶體區域的劃分,簡單的畫了下方的這個示意圖。區域主要分為兩大塊,一塊是堆區(Heap),我們所New出的物件都會在堆區進行分配,在C語言中的malloc所分配的方法就是從Heap區獲取的。而垃圾回收器主要是對堆區的記憶體進行回收的。

而另一部分則是非堆區,非堆區主要包括用於編譯和儲存原生代碼的“程式碼快取區(Code Cache)”、儲存JVM自己的靜態資料的“永生代(Perm Gen)”、存放方法引數區域性變數等引用以及記錄方法呼叫順序的“Java虛擬機器棧(JVM Stack)”和“本地方法棧(Local Method Stack)”。

垃圾回收器主要回收的是堆區中未使用的記憶體區域,並對相應的區域進行整理。在堆區中,又根據物件記憶體的存活時間或者物件大小,分為“年輕代”和“年老代”。“年輕代”中的物件是不穩定的易產生垃圾,而“年老代”中的物件比較穩定,不易產生垃圾。之所以將其分開,是分而治之,根據不同區域的記憶體塊的特點,採取不同的記憶體回收演算法,從而提高堆區的垃圾回收的效率。

永久代存放JVM執行時使用的類。永久代同樣包含了Java SE庫的類和方法。永久代的物件在full GC時進行垃圾收集。

一、元空間替換持久代

1.1、持久代

  PermGen space的全稱是Permanent Generation space,是指記憶體的永久儲存區域,說說為什麼會記憶體益出:這一部分用於存放Class和Meta的資訊,Class在被 Load的時候被放入PermGen space區域,它和和存放Instance的Heap區域不同,所以如果你的APP會LOAD很多CLASS的話,就很可能出現PermGen space錯誤。這種錯誤常見在web伺服器對JSP進行pre compile的時候。

  JVM 種類有很多,比如 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘寶好樣的!)等等。當然武林盟主是Hotspot了,這個毫無爭議。需要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是沒有這個區域。

持久代中包含了虛擬機器中所有可通過反射獲取到的資料,比如Class和Method物件。不同的Java虛擬機器之間可能會進行類共享,因此持久代又分為只讀區和讀寫區。

JVM用於描述應用程式中用到的類和方法的元資料也儲存在持久代中。JVM執行時會用到多少持久代的空間取決於應用程式用到了多少類。除此之外,Java SE庫中的類和方法也都儲存在這裡。

如果JVM發現有的類已經不再需要了,它會去回收(解除安裝)這些類,將它們的空間釋放出來給其它類使用。Full GC會進行持久代的回收。

  • JVM中類的元資料在Java堆中的儲存區域。
  • Java類對應的HotSpot虛擬機器中的內部表示也儲存在這裡。
  • 類的層級資訊,欄位,名字。
  • 方法的編譯資訊及位元組碼。
  • 變數
  • 常量池和符號解析

持久代的大小

  • 它的上限是MaxPermSize,預設是64M
  • Java堆中的連續區域 : 如果儲存在非連續的堆空間中的話,要定位出持久代到新物件的引用非常複雜並且耗時。卡表(card table),是一種記憶集(Remembered Set),它用來記錄某個記憶體代中普通物件指標(oops)的修改。
  • 持久代用完後,會丟擲OutOfMemoryError "PermGen space"異常。解決方案:應用程式清理引用來觸發類解除安裝;增加MaxPermSize的大小。
  • 需要多大的持久代空間取決於類的數量,方法的大小,以及常量池的大小。

1.2、為什麼移除持久代

  • 它的大小是在啟動時固定好的——很難進行調優。-XX:MaxPermSize,設定成多少好呢?
  • HotSpot的內部型別也是Java物件:它可能會在Full GC中被移動,同時它對應用不透明,且是非強型別的,難以跟蹤除錯,還需要儲存元資料的元資料資訊(meta-metadata)。
  • 簡化Full GC:每一個回收器有專門的元資料迭代器。
  • 可以在GC不進行暫停的情況下併發地釋放類資料。
  • 使得原來受限於持久代的一些改進未來有可能實現

 

根據上面的各種原因,永久代最終被移除,方法區移至Metaspace,字串常量移至Java Heap

1.3、移除持久代後,PermGen空間的狀況

  • 這部分記憶體空間將全部移除。

  • JVM的引數:PermSize 和 MaxPermSize 會被忽略並給出警告(如果在啟用時設定了這兩個引數)。

二、元空間

隨著JDK8的到來,JVM不再有PermGen。但類的元資料資訊(metadata)還在,只不過不再是儲存在連續的堆空間上,而是移動到叫做“Metaspace”的本地記憶體(Native memory)中。

2.1、metaspace的組成

  • Klass 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:NoKlass Metaspace專門來存klass相關的其他的內容,比如method,constantPool等,這塊記憶體是由多塊記憶體組合起來的,所以可以認為是不連續的記憶體塊組成的。這塊記憶體是必須的,雖然叫做NoKlass Metaspace,但是也其實可以存klass的內容,上面已經提到了對應場景。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以類載入器們要分配記憶體,但是每個類載入器都有一個SpaceManager,來管理屬於這個類載入的記憶體小塊。如果Klass Metaspace用完了,那就會OOM了,不過一般情況下不會,NoKlass Mestaspace是由一塊塊記憶體慢慢組合起來的,在沒有達到限制條件的情況下,會不斷加長這條鏈,讓它可以持續工作。

元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小: 
  -XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。 
  -XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。 
  除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性: 
  -XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集 
  -XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集

  -verbose引數是為了獲取型別載入和解除安裝的資訊

2.2、元空間的特點

  • 充分利用了Java語言規範中的好處:類及相關的元資料的生命週期與類載入器的一致。
  • 每個載入器有專門的儲存空間
  • 只進行線性分配
  • 不會單獨回收某個類
  • 省掉了GC掃描及壓縮的時間
  • 元空間裡的物件的位置是固定的
  • 如果GC發現某個類載入器不再存活了,會把相關的空間整個回收掉

2.3、元空間的記憶體分配模型

  • 絕大多數的類元資料的空間都從本地記憶體中分配
  • 用來描述類元資料的類(klasses)也被刪除了
  • 分元資料分配了多個虛擬記憶體空間
  • 給每個類載入器分配一個記憶體塊的列表。塊的大小取決於類載入器的型別; sun/反射/代理對應的類載入器的塊會小一些
  • 歸還記憶體塊,釋放記憶體塊列表
  • 一旦元空間的資料被清空了,虛擬記憶體的空間會被回收掉
  • 減少碎片的策略

我們來看下JVM是如何給元資料分配虛擬記憶體的空間的 

你可以看到虛擬記憶體空間是如何分配的(vs1,vs2,vs3) ,以及類載入器的記憶體塊是如何分配的。CL是Class Loader的縮寫。

理解_mark和_klass指標

要想理解下面這張圖,你得搞清楚這些指標都是什麼東西。

JVM中,每個物件都有一個指向它自身類的指標,不過這個指標只是指向具體的實現類,而不是介面或者抽象類。

對於32位的JVM:

_mark : 4位元組常量

_klass: 指向類的4位元組指標 物件的記憶體佈局中的第二個欄位( _klass,在32位JVM中,相對物件在記憶體中的位置的偏移量是4,64位的是8)指向的是記憶體中物件的類定義。

64位的JVM:

_mark : 8位元組常量

_klass: 指向類的8位元組的指標

開啟了指標壓縮的64位JVM: _mark : 8位元組常量

_klass: 指向類的4位元組的指標

Java物件的記憶體佈局

類指標壓縮空間(Compressed Class Pointer Space)

只有是64位平臺上啟用了類指標壓縮才會存在這個區域。對於64位平臺,為了壓縮JVM物件中的_klass指標的大小,引入了類指標壓縮空間(Compressed Class Pointer Space)。

壓縮指標後的記憶體佈局

指標壓縮概要

  • 64位平臺上預設開啟
  • 使用-XX:+UseCompressedOops壓縮物件指標 "oops"指的是普通物件指標("ordinary" object pointers)。 Java堆中物件指標會被壓縮成32位。 使用堆基地址(如果堆在低26G記憶體中的話,基地址為0)

  • 使用-XX:+UseCompressedClassPointers選項來壓縮類指標

  • 物件中指向類元資料的指標會被壓縮成32位

  • 類指標壓縮空間會有一個基地址

元空間和類指標壓縮空間的區別

  • 類指標壓縮空間只包含類的元資料,比如InstanceKlass, ArrayKlass 僅當打開了UseCompressedClassPointers選項才生效 為了提高效能,Java中的虛方法表也存放到這裡 這裡到底存放哪些元資料的型別,目前仍在減少

  • 元空間包含類的其它比較大的元資料,比如方法,位元組碼,常量池等。

三、元空間記憶體管理

元空間的記憶體管理由元空間虛擬機器來完成。先前,對於類的元資料我們需要不同的垃圾回收器進行處理,現在只需要執行元空間虛擬機器的C++程式碼即可完成。在元空間中,類和其元資料的生命週期和其對應的類載入器是相同的。話句話說,只要類載入器存活,其載入的類的元資料也是存活的,因而不會被回收掉。 
準確的來說,每一個類載入器的儲存區域都稱作一個元空間,所有的元空間合在一起就是我們一直說的元空間。當一個類載入器被垃圾回收器標記為不再存活,其對應的元空間會被回收。在元空間的回收過程中沒有重定位和壓縮等操作。但是元空間內的元資料會進行掃描來確定Java引用。 
元空間虛擬機器負責元空間的分配,其採用的形式為組塊分配。組塊的大小因類載入器的型別而異。在元空間虛擬機器中存在一個全域性的空閒組塊列表。當一個類載入器需要組塊時,它就會從這個全域性的組塊列表中獲取並維持一個自己的組塊列表。當一個類載入器不再存活,那麼其持有的組塊將會被釋放,並返回給全域性組塊列表。類載入器持有的組塊又會被分成多個塊,每一個塊儲存一個單元的元資訊。組塊中的塊是線性分配(指標碰撞分配形式)。組塊分配自記憶體對映區域。這些全域性的虛擬記憶體對映區域以連結串列形式連線,一旦某個虛擬記憶體對映區域清空,這部分記憶體就會返回給作業系統。

上圖展示的是虛擬記憶體對映區域如何進行元組塊的分配。類載入器1和3表明使用了反射或者為匿名類載入器,他們使用了特定大小組塊。 而類載入器2和4根據其內部條目的數量使用小型或者中型的組塊。

四、Metaspace調優

使用-XX:MaxMetaspaceSize引數可以設定元空間的最大值,預設是沒有上限的,也就是說你的系統記憶體上限是多少它就是多少。-XX:MetaspaceSize選項指定的是元空間的初始大小,如果沒有指定的話,元空間會根據應用程式執行時的需要動態地調整大小。

MaxMetaspaceSize的調優

  • -XX:MaxMetaspaceSize={unlimited}
  • 元空間的大小受限於你機器的記憶體
  • 限制類的元資料使用的記憶體大小,以免出現虛擬記憶體切換以及本地記憶體分配失敗。如果懷疑有類載入器出現洩露,應當使用這個引數;32位機器上,如果地址空間可能會被耗盡,也應當設定這個引數。
  • 元空間的初始大小是21M——這是GC的初始的高水位線,超過這個大小會進行Full GC來進行類的回收。
  • 如果啟動後GC過於頻繁,請將該值設定得大一些
  • 可以設定成和持久代一樣的大小,以便推遲GC的執行時間

CompressedClassSpaceSize的調優

  • 只有當-XX:+UseCompressedClassPointers開啟了才有效
  • -XX:CompressedClassSpaceSize=1G
  • 由於這個大小在啟動的時候就固定了的,因此最好設定得大點。
  • 沒有使用到的話不要進行設定
  • JVM後續可能會讓這個區可以動態的增長。不需要是連續的區域,只要從基地址可達就行;可能會將更多的類元資訊放回到元空間中;未來會基於PredictedLoadedClassCount的值來自動的設定該空間的大小

正如前面提到了,Metaspace VM管理Metaspace空間的增長。但有時你會想通過在命令列顯示的設定引數-XX:MaxMetaspaceSize來限制Metaspace空間的增長。預設情況下,-XX:MaxMetaspaceSize並沒有限制,因此,在技術上,Metaspace的尺寸可以增長到交換空間,而你的本地記憶體分配將會失敗。

每次垃圾收集之後,Metaspace VM會自動的調整high watermark,推遲下一次對Metaspace的垃圾收集。

這兩個引數,-XX:MinMetaspaceFreeRatio和-XX:MaxMetaspaceFreeRatio,類似於GC的FreeRatio引數,可以放在命令列。

五、Metaspace可以使用的工具

針對Metaspace,JDK自帶的一些工具做了修改來展示Metaspace的資訊:

  • jmap -clstats :列印類載入器的統計資訊(取代了在JDK8之前列印類載入器資訊的permstat)。
  • jstat -gc :Metaspace的資訊也會被打印出來。
  • jcmd GC.class_stats:這是一個新的診斷命令,可以使使用者連線到存活的JVM,轉儲Java類元資料的詳細統計。

示例1:jmap -clstats 

複製程式碼

[ciadmin@2-103test_app pos-gateway-cloud]$ jmap -clstats 26964
Attaching to process ID 26964, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness..................................................................................liveness analysis may be inaccurate ...
class_loader    classes    bytes    parent_loader    alive?    type

<bootstrap>    2699    4611703      null      live    <internal>
0x00000000a1013a00    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a3e931e8    1    880      null      dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d280    1    1471    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1c057c8    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1013938    1    1474    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a1013d38    1    1471    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a141ae78    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d1b8    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a163c658    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a293afa8    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a19ec0a0    15    70893    0x00000000a001b938    live    com/aliyun/openservices/shade/com/alibaba/fastjson/util/ASMClassLoader@0x000000010066b7a0
0x00000000a2778848    1    1474    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a141a900    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a083d8c0    1    1473    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
0x00000000a163c720    1    880    0x00000000a001b938    dead    sun/reflect/DelegatingClassLoader@0x0000000100009df8
...
0x00000000a094fe68    0    0    0x00000000a0007438    live    java/net/URLClassLoader@0x000000010000ecd0

total = 177    12836    20539140        N/A        alive=9, dead=168        N/A    
[ciadmin@2-103test_app pos-gateway-cloud]$

複製程式碼

示例二:jstat -gc 26964

[ciadmin@2-103test_app pos-gateway-cloud]$ jstat -gc 26964
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
3072.0 3072.0 2384.8  0.0   62976.0   6699.1   445440.0   67911.5   69760.0 68124.8 8320.0 7929.0   3792   36.649  12      1.971   38.620
[ciadmin@2-103test_app pos-gateway-cloud]$

示例三:jcmd 5943 GC.class_stats

[ciadmin@2-103test_app pos-gateway-cloud]$ jcmd 5943 GC.class_stats
5943:
GC.class_stats command requires -XX:+UnlockDiagnosticVMOptions
[ciadmin@2-103test_app pos-gateway-cloud]$

說是:應用程式啟動時增加-XX:+UnlockDiagnosticVMOptions引數

加了上面的引數後,重新來一把如下:

複製程式碼

D:\workspace\study\target\classes\com\dxz\jvm>jcmd 4332 GC.class_stats
4332:
Index Super  InstBytes KlassBytes annotations  CpAll MethodCount Bytecodes MethodAll  ROAll   RWAll   Total ClassName
    1    -1  258458040        480           0      0           0         0         0     24     584     608 [Ljava.lang.Object;
    2   368  217343856       1000           0   6864          51      3955     13744   8664   13888   22552 java.util.HashMap
    3   368  144895744       1432           0  15584          93      9536     37136  19104   37048   56152 java.util.concurrent.ConcurrentHashMap
    4   363   99615560        928           0   8232          24      1719      6040   4392   11304   15696 java.net.URLClassLoader
    5    -1   90560256        480           0      0           0         0         0     32     584     616 [Ljava.util.concurrent.ConcurrentHashMap$Node;
    6    -1   90559840        480           0      0           0         0         0     32     584     616 [Ljava.util.WeakHashMap$Entry;
    7   367   72447872       1384           0   5288          59      2245     13544   7080   14016   21096 java.util.Vector
    8   367   54335928       1320           0   4936          49      2359     12104   6600   12592   19192 java.util.ArrayList
    9   368   54335904        976           0   4952          32      1845     11968   4856   13744   18600 java.util.WeakHashMap
   10    14   54335856        656           0   7488          33      1513      8504   4792   12496   17288 sun.misc.URLClassPath
   11    14   45280240        504           0   4960          27      2551     11792   5232   12536   17768 java.security.AccessControlContext
   12    14   45279920        528           0   4328          12      1024      3760   2488    6496    8984 java.security.ProtectionDomain
   13    14   36226208        568           0   1344           8       223      1744   1024    2952    3976 java.util.concurrent.ConcurrentHashMap$Node
   14    -1   36224752        496           0   1144          14       109      2520   1112    3272    4384 java.lang.Object
   15    14   36224032        552           0   1840           7       410      2744   1288    4160    5448 java.lang.ref.ReferenceQueue
   16    14   36223936        552           0   5320          14      1796      4648   3552    7328   10880 java.security.CodeSource
   17     7   36223936       1424           0    864           6        88      1664    704    3552    4256 java.util.Stack
   18   373   27167952       1008           0    808           4        69      1000    592    2528    3120 java.util.Collections$SynchronizedSet
   19    -1   27167880        480           0      0           0         0         0     24     584     608 [Ljava.security.ProtectionDomain;
   20    14   18112048        496           0    360           2        10       920    216    1720    1936 java.lang.ref.ReferenceQueue$Lock
   21    -1   18111968        480           0      0           0         0         0     24     584     608 [Ljava.security.Principal;
...
  484    14          0        496           0   1416          20       737      3736   2240    3680    5920 sun.util.locale.LocaleUtils
            1535868936     298000        1536 955600        6934    264621   1445736 885528 1980368 2865896 Total
              53591.2%      10.4%        0.1%  33.3%           -      9.2%     50.4%  30.9%   69.1%  100.0%
Index Super  InstBytes KlassBytes annotations  CpAll MethodCount Bytecodes MethodAll  ROAll   RWAll   Total ClassName

D:\workspace\study\target\classes\com\dxz\jvm>

複製程式碼

六、提高GC的效能

如果你理解了元空間的概念,很容易發現GC的效能得到了提升。

  • Full GC中,元資料指向元資料的那些指標都不用再掃描了。很多複雜的元資料掃描的程式碼(尤其是CMS裡面的那些)都刪除了。
  • 元空間只有少量的指標指向Java堆。這包括:類的元資料中指向java/lang/Class例項的指標;陣列類的元資料中,指向java/lang/Class集合的指標。
  • 沒有元資料壓縮的開銷
  • 減少了根物件的掃描(不再掃描虛擬機器裡面的已載入類的字典以及其它的內部雜湊表)
  • 減少了Full GC的時間
  • G1回收器中,併發標記階段完成後可以進行類的解除安裝

java8中metaspace總結如下:

PermGen 空間的狀況

這部分記憶體空間將全部移除。

JVM的引數:PermSize 和 MaxPermSize 會被忽略並給出警告(如果在啟用時設定了這兩個引數)。

Metaspace 記憶體分配模型

大部分類元資料都在本地記憶體中分配。

用於描述類元資料的“klasses”已經被移除。

Metaspace 容量

預設情況下,類元資料只受可用的本地記憶體限制(容量取決於是32位或是64位作業系統的可用虛擬記憶體大小)。

新引數(MaxMetaspaceSize)用於限制本地記憶體分配給類元資料的大小。如果沒有指定這個引數,元空間會在執行時根據需要動態調整。

Metaspace 垃圾回收

對於僵死的類及類載入器的垃圾回收將在元資料使用達到“MaxMetaspaceSize”引數的設定值時進行。

適時地監控和調整元空間對於減小垃圾回收頻率和減少延時是很有必要的。持續的元空間垃圾回收說明,可能存在類、類載入器導致的記憶體洩漏或是大小設定不合適。

七、元空間的問題

前面已經提到,元空間虛擬機器採用了組塊分配的形式,同時區塊的大小由類載入器型別決定。類資訊並不是固定大小,因此有可能分配的空閒區塊和類需要的區塊大小不同,這種情況下可能導致碎片存在。元空間虛擬機器目前並不支援壓縮操作,所以碎片化是目前最大的問題。

&