本文来自: PerfMa技术社区PerfMa(笨马网络)官网
概述
metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来代替perm,这块空间很有本人的特点,前段时间公司这块的问题太多了,次要是因为降级了中间件所致,看到大家探讨来探讨去,看得出很多人对metaspace还是不置可否,不是很理解它,因而我感觉有必要写篇文章来介绍一下它,解开它神秘的面纱,当咱们再次碰到它的相干问题的时候不会再感到大刀阔斧。
通过这篇文章,你将能够理解到
- 为什么会有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问题。
metaspace的组成
metaspace其实由两大部分组成
- 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是由一块块内存缓缓组合起来的,在没有达到限度条件的状况下,会一直加长这条链,让它能够继续工作。
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相干的这么一些指标,别离是M
,CCS
,MC
,MU
,CCSC
,CCSU
,MCMN
,MCMX
,CCSMN
,CCSMX
它们的定义如下:
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问题排查,又让我涨姿态了!