Metaspace 之一:Metaspace总体介绍(永久代被替换缘由、元空间特色、元空间内存查看分析方法)

回顾

根据JVM内存区域的划分,简单的画了下方的这个示意图。区域主要分为两大块,一块是堆区(Heap),咱们所New出的对象都会在堆区进行分配,在C语言中的malloc所分配的方法就是从Heap区获取的。而垃圾回收器主要是对堆区的内存进行回收的。html

而另外一部分则是非堆区,非堆区主要包括用于编译和保存本地代码的“代码缓存区(Code Cache)”、保存JVM本身的静态数据的“永生代(Perm Gen)”、存放方法参数局部变量等引用以及记录方法调用顺序的“Java虚拟机栈(JVM Stack)”和“本地方法栈(Local Method Stack)”。java

垃圾回收器主要回收的是堆区中未使用的内存区域,并对相应的区域进行整理。在堆区中,又根据对象内存的存活时间或者对象大小,分为“年轻代”和“年老代”。“年轻代”中的对象是不稳定的易产生垃圾,而“年老代”中的对象比较稳定,不易产生垃圾。之因此将其分开,是分而治之,根据不一样区域的内存块的特色,采起不一样的内存回收算法,从而提升堆区的垃圾回收的效率。web

永久代存放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的时候。json

  JVM 种类有不少,好比 Oralce-Sun Hotspot, Oralce JRockit, IBM J9, Taobao JVM(淘宝好样的!)等等。固然武林盟主是Hotspot了,这个毫无争议。须要注意的是,PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。bootstrap

持久代中包含了虚拟机中全部可经过反射获取到的数据,好比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.二、为何移除持久代

  • 它的大小是在启动时固定好的——很难进行调优。-XX:MaxPermSize,设置成多少好呢?
  • HotSpot的内部类型也是Java对象:它可能会在Full GC中被移动,同时它对应用不透明,且是非强类型的,难以跟踪调试,还须要存储元数据的元数据信息(meta-metadata)。
  • 简化Full GC:每个回收器有专门的元数据迭代器。
  • 能够在GC不进行暂停的状况下并发地释放类数据。
  • 使得原来受限于持久代的一些改进将来有可能实现

根据上面的各类缘由,永久代最终被移除,方法区移至Metaspace,字符串常量移至Java Heap

1.三、移除持久代后,PermGen空间的情况

  • 这部份内存空间将所有移除。

  • JVM的参数:PermSize 和 MaxPermSize 会被忽略并给出警告(若是在启用时设置了这两个参数)。

 

 

2、元空间

随着JDK8的到来,JVM再也不有PermGen。但类的元数据信息(metadata)还在,只不过再也不是存储在连续的堆空间上,而是移动到叫作“Metaspace”的本地内存(Native memory)中。

2.一、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.二、元空间的特色

  • 充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致
  • 每一个加载器有专门的存储空间
  • 只进行线性分配
  • 不会单独回收某个类
  • 省掉了GC扫描及压缩的时间
  • 元空间里的对象的位置是固定的
  • 若是GC发现某个类加载器再也不存活了,会把相关的空间整个回收掉

2.三、元空间的内存分配模型

  • 绝大多数的类元数据的空间都从本地内存中分配
  • 用来描述类元数据的类(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)。

CompressedClassSpace

  • java8移除了permanent generation,而后class metadata存储在native memory中,其大小默认是不受限的,能够经过-XX:MaxMetaspaceSize来限制
  • 若是开启了-XX:+UseCompressedOops及-XX:+UseCompressedClassesPointers(默认是开启),则UseCompressedOops会使用32-bit的offset来表明java object的引用,而UseCompressedClassPointers则使用32-bit的offset来表明64-bit进程中的class pointer;可使用CompressedClassSpaceSize来设置这块的空间大小
  • 若是开启了指针压缩,则CompressedClassSpace分配在MaxMetaspaceSize里头,即MaxMetaspaceSize=Compressed Class Space Size + Metaspace area (excluding the Compressed Class Space) Size
  • 查看CompressedClassSpace大小,见《12、jdk工具之jcmd介绍(堆转储、堆分析、获取系统信息、查看堆外内存)

压缩指针后的内存布局

指针压缩概要

  • 64位平台上默认打开
  • 使用-XX:+UseCompressedOops压缩对象指针 "oops"指的是普通对象指针("ordinary" object pointers)。 Java堆中对象指针会被压缩成32位。 使用堆基地址(若是堆在低26G内存中的话,基地址为0)

  • 使用-XX:+UseCompressedClassPointers选项来压缩类指针

  • 对象中指向类元数据的指针会被压缩成32位

  • 类指针压缩空间会有一个基地址

元空间和类指针压缩空间的区别

  • 类指针压缩空间只包含类的元数据,好比InstanceKlass, ArrayKlass 仅当打开了UseCompressedClassPointers选项才生效 为了提升性能,Java中的虚方法表也存放到这里 这里到底存放哪些元数据的类型,目前仍在减小

  • 元空间包含类的其它比较大的元数据,好比方法,字节码,常量池等。

3、元空间内存管理

元空间的内存管理由元空间虚拟机来完成。先前,对于类的元数据咱们须要不一样的垃圾回收器进行处理,如今只须要执行元空间虚拟机的C++代码便可完成。在元空间中,类和其元数据的生命周期和其对应的类加载器是相同的。话句话说,只要类加载器存活,其加载的类的元数据也是存活的,于是不会被回收掉。 
准确的来讲,每个类加载器的存储区域都称做一个元空间,全部的元空间合在一块儿就是咱们一直说的元空间。当一个类加载器被垃圾回收器标记为再也不存活,其对应的元空间会被回收。在元空间的回收过程当中没有重定位和压缩等操做。可是元空间内的元数据会进行扫描来肯定Java引用。 
元空间虚拟机负责元空间的分配,其采用的形式为组块分配。组块的大小因类加载器的类型而异。在元空间虚拟机中存在一个全局的空闲组块列表。当一个类加载器须要组块时,它就会从这个全局的组块列表中获取并维持一个本身的组块列表。当一个类加载器再也不存活,那么其持有的组块将会被释放,并返回给全局组块列表。类加载器持有的组块又会被分红多个块,每个块存储一个单元的元信息。组块中的块是线性分配(指针碰撞分配形式)。组块分配自内存映射区域。这些全局的虚拟内存映射区域以链表形式链接,一旦某个虚拟内存映射区域清空,这部份内存就会返回给操做系统。

上图展现的是虚拟内存映射区域如何进行元组块的分配。类加载器1和3代表使用了反射或者为匿名类加载器,他们使用了特定大小组块。 而类加载器2和4根据其内部条目的数量使用小型或者中型的组块。

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参数,能够放在命令行。

5、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>

6、提升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”参数的设定值时进行。

适时地监控和调整元空间对于减少垃圾回收频率和减小延时是颇有必要的。持续的元空间垃圾回收说明,可能存在类、类加载器致使的内存泄漏或是大小设置不合适。

7、元空间的问题

前面已经提到,元空间虚拟机采用了组块分配的形式,同时区块的大小由类加载器类型决定。类信息并非固定大小,所以有可能分配的空闲区块和类须要的区块大小不一样,这种状况下可能致使碎片存在。元空间虚拟机目前并不支持压缩操做,因此碎片化是目前最大的问题。

相关文章
相关标签/搜索