Metaspace 之一:Metaspace整體介紹(永久代被替換原因、元空間特點、元空間內存查看分析方法)

本文轉自:

https://www.cnblogs.com/williamjie/p/9558094.html

一、元空間替換持久代

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”參數的設定值時進行。

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

七、元空間的問題

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

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