Metaspace之⼀:Metaspace整体介绍(永久代被替换原因、元
空间特点、元空间内。。。
⼀、元空间替换持久代
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:CompressedCl
assSpaceSize参数来控制,这个参数前⾯提到了默认是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的垃圾收集。
jvm调优参数
这两个参数,-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,
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. 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@0x0000000
10000ecd0
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;