JVM源码分析之Metaspace解密

35次阅读

共计 6840 个字符,预计需要花费 18 分钟才能阅读完成。

本文来自: 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 相干的这么一些指标,别离是MCCSMCMUCCSCCCSUMCMNMCMXCCSMNCCSMX

它们的定义如下:

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 问题排查,又让我涨姿态了!

正文完
 0