本文来自: PerfMa技术社区PerfMa(笨马网络)官网javascript
metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间颇有本身的特色,前段时间公司这块的问题太多了,主要是由于升级了中间件所致,看到你们讨论来讨论去,看得出不少人对metaspace仍是模棱两可,不是很了解它,所以我以为有必要写篇文章来介绍一下它,解开它神秘的面纱,当咱们再次碰到它的相关问题的时候不会再感到一筹莫展。java
经过这篇文章,你将能够了解到bootstrap
metaspace的由来民间已有不少传说,不过我这里只谈我本身的理解,由于我不是oracle参与这块的开发者,因此对其真正的由来不怎么了解。网络
咱们都知道jdk8以前有perm这一整块内存来存klass等信息,咱们的参数里也必不可少地会配置-XX:PermSize以及-XX:MaxPermSize来控制这块内存的大小,jvm在启动的时候会根据这些配置来分配一块连续的内存块,可是随着动态类加载的状况愈来愈多,这块内存咱们变得不太可控,到底设置多大合适是每一个开发者要考虑的问题,若是设置过小了,系统运行过程当中就容易出现内存溢出,设置大了又总感受浪费,尽管不会实质分配这么大的物理内存。基于这么一个可能的缘由,因而metaspace出现了,但愿内存的管理再也不受到限制,也不要怎么关注元数据这块的OOM问题,虽然到目前来看,也并无完美地解决这个问题。数据结构
或许从JVM代码里也能看出一些端倪来,好比MaxMetaspaceSize
默认值很大,CompressedClassSpaceSize
默认也有1G,从这些参数咱们能猜到metaspace的做者不但愿出现它相关的OOM问题。oracle
metaspace其实由两大部分组成jvm
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是由一块块内存慢慢组合起来的,在没有达到限制条件的状况下,会不断加长这条链,让它能够持续工做。spa
若是咱们要改变metaspace的一些行为,咱们通常会对其相关的一些参数作调整,由于metaspace的参数自己不是不少,因此我这里将涉及到的全部参数都作一个介绍,也许好些参数你们都是有误解的
默认false,这个参数是说是否在metaspace里使用LargePage,通常状况下咱们使用4KB的page size,这个参数依赖于UseLargePages这个参数开启,不过这个参数咱们通常不开。
64位下默认4M,32位下默认2200K,metasapce前面已经提到主要分了两大块,Klass Metaspace以及NoKlass Metaspace,而NoKlass Metaspace是由一块块内存组合起来的,这个参数决定了NoKlass Metaspace的第一个内存Block的大小,即2*InitialBootClassLoaderMetaspaceSize,同时为bootstrapClassLoader的第一块内存chunk分配了InitialBootClassLoaderMetaspaceSize的大小。
默认20.8M左右(x86下开启c2模式),主要是控制metaspaceGC发生的初始阈值,也是最小阈值,可是触发metaspaceGC的阈值是不断变化的,与之对比的主要是指Klass Metaspace与NoKlass Metaspace两块committed的内存和。
默认基本是无穷大,可是我仍是建议你们设置这个参数,由于极可能会由于没有限制而致使metaspace被无止境使用(通常是内存泄漏)而被OS Kill。这个参数会限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的内存大小,会保证committed的内存不会超过这个值,一旦超过就会触发GC,这里要注意和MaxPermSize的区别,MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来,而MaxPermSize是会分配一块这么大的内存的。
默认1G,这个参数主要是设置Klass Metaspace的大小,不过这个参数设置了也不必定起做用,前提是能开启压缩指针,假如-Xmx超过了32G,压缩指针是开启不来的。若是有Klass Metaspace,那这块内存是和Heap连着的。
MinMetaspaceExpansion和MaxMetaspaceExpansion这两个参数或许和你们认识的并不同,也许不少人会认为这两个参数不就是内存不够的时候,而后扩容的最小大小吗?其实否则
这两个参数和扩容其实并无直接的关系,也就是并非为了增大committed的内存,而是为了增大触发metaspace GC的阈值
这两个参数主要是在比较特殊的场景下救急使用,好比gcLocker或者should_concurrent_collect的一些场景,由于这些场景下接下来会作一次GC,相信在接下来的GC中可能会释放一些metaspace的内存,因而先临时扩大下metaspace触发GC的阈值,而有些内存分配失败其实正好是由于这个阈值触顶致使的,因而能够经过增大阈值暂时绕过去
默认332.8K,增大触发metaspace GC阈值的最小要求。假如咱们要救急分配的内存很小,没有达到MinMetaspaceExpansion,可是咱们会将此次触发metaspace GC的阈值提高MinMetaspaceExpansion,之因此要大于此次要分配的内存大小主要是为了防止别的线程也有相似的请求而频繁触发相关的操做,不过若是要分配的内存超过了MaxMetaspaceExpansion,那MinMetaspaceExpansion将会是要分配的内存大小基础上的一个增量。
默认5.2M,增大触发metaspace GC阈值的最大要求。假如说咱们要分配的内存超过了MinMetaspaceExpansion可是低于MaxMetaspaceExpansion,那增量是MaxMetaspaceExpansion,若是超过了MaxMetaspaceExpansion,那增量是MinMetaspaceExpansion加上要分配的内存大小
注:每次分配只会给对应的线程一次扩展触发metaspace GC阈值的机会,若是扩展了,可是还不能分配,那就只能等着作GC了。
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。
默认70,这个参数和上面的参数基本是相反的,是为了不触发metaspaceGC的阈值过大,而想对这个值进行缩小。这个参数在gc以后committed的内存比较小的时候而且离触发metaspaceGC的阈值比较远的时候,调整会比较明显。
咱们看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" }
我这里对这些字段分类介绍下
PS:因此咱们有时候看到M的值达到了90%以上,其实这个并不必定说明metaspace用了不少了,由于内存是慢慢commit的,因此咱们的分母是慢慢变大的,不过当咱们committed到必定量的时候就不会再增加了
综上所述,其实看metaspace最主要的仍是看MC,MU,CCSC,CCSU这几个具体的大小来判断metaspace到底用了多少更靠谱
原本还想写metaspace内存分配和GC的内容,不过那块提及来又是一个比较大的话题,由于那块你们看起来可能会比较枯燥,有机会再写
一块儿来学习吧: