JVM完全解读之Metaspace解密源码分析

概述

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这块的实现,有兴趣的可以到我的公众号你假笨里去翻阅下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两者总共的使用率,其实可以根据上面的四个指标算出来,即(CCSU+MU)/(CCSC+MC)
  • 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最主要的还是看MCMUCCSCCCSU这几个具体的大小来判断metaspace到底用了多少更靠谱

以上就是JVM完全解读之Metaspace解密源码分析的详细内容,更多关于Metaspace源码解密的资料请关注我们其它相关文章!

(0)

相关推荐

  • 一篇文章带你深入理解JVM虚拟机读书笔记--锁优化

    目录 1. Java语言中的线程安全 1.1 不可变 1.2 绝对线程安全 1.3 相对线程安全 1.4 线程兼容 1.5 线程对立 2. 线程安全的实现方法 2.1 互斥同步 3. 锁优化 3.1 自旋锁与自适应自旋 3.2 锁消除 3.3 锁粗化 3.4 轻量级锁 3.5 偏向锁 总结 1. Java语言中的线程安全 按照线程安全的"安全程度"由强至弱来排序,可以将Java语言中各种操作共享的数据分为以下五类:不可变.绝对线程安全.相对线程安全.线程兼容和线程对立. 1.1 不可变

  • JVM中四种GC算法案例详解

    目录 介绍 引用计数算法(Reference counting) 算法思想: 核心思想: 优点: 缺点: 例子如图: 标记–清除算法(Mark-Sweep) 算法思想: 优点 缺点 例子如图 标记–整理算法 算法思想 优点 缺点 例子 复制算法 算法思想 优点 缺点 总结 介绍 程序在运行过程中,会产生大量的内存垃圾(一些没有引用指向的内存对象都属于内存垃圾,因为这些对象已经无法访问,程序用不了它们了,对程序而言它们已经死亡),为了确保程序运行时的性能,java虚拟机在程序运行的过程中不断地进行

  • 详解JVM中的GC调优

    那些GC的默认值 其实GC或者说JVM的参数非常非常的多,有控制内存使用的: 有控制JIT的: 有控制分代比例的,也有控制GC并发的: 当然,大部分的参数其实并不需要我们自行去调整,JVM会很好的动态帮我们设置这些变量的值. 如果我们不去设置这些值,那么对GC性能比较有影响的参数和他们的默认值有哪些呢? GC的选择 我们知道JVM中的GC有很多种,不同的GC选择对java程序的性能影响还是比较大的. 在JDK9之后,G1已经是默认的垃圾回收器了. 我们看一下G1的调优参数. G1是基于分代技术的

  • Java8内存模型PermGen Metaspace实例解析

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫"栈帧"的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表(基本数据类型和对象引用).操作数栈.方法出口等信息.栈的大小可以固定也可以动态扩展.当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误,不过这个深度范围不是一个恒定的值,我们通过下面这段程序可以测

  • JVM Metaspace内存溢出问题解决方案

    一. 现象 前段时间公司线上环境的一个Java应用因为OOM的异常报警,导致整个服务不可用被拉出集群,本地模拟重现的现象如下: 当时的解决方案是增加metaspace的容量:-XX:MaxMetaspaceSize=500m,从原来默认的256m改为500m,虽然没有再出现oom,但这个只是临时解决方案,通过公司的监控系统观察metaspace的使用情况还是在上升,而且后面随着业务访问量越来越大还是有可能达到阈值. 二. 分析 Metaspace元空间主要是存储类的元数据信息,我们的应用里加载的

  • JVM完全解读之Metaspace解密源码分析

    概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所致,看到大家讨论来讨论去,看得出很多人对metaspace还是模棱两可,不是很了解它,因此我觉得有必要写篇文章来介绍一下它,解开它神秘的面纱,当我们再次碰到它的相关问题的时候不会再感到束手无策. 通过这篇文章,你将可以了解到 为什么会有metaspace metaspace的组成 metaspace的VM参

  • php源码分析之DZX1.5加密解密函数authcode用法

    本文实例讲述了php源码分析之DZX1.5加密解密函数authcode用法.分享给大家供大家参考.具体分析如下: <?php $authkey = ''; /** * @param string $string: 输入的需要加密(或解密)的明文(或密文) * @param string $operation: 'DECODE'或其它,其中默认表示解密,输入其它表示加密 * @param string $key: 加解密密钥 * @param int $expiry: 有效期 */ functio

  • python实现AES算法及AES-CFB8加解密源码

    目录 Python实现AES算法 生成轮密钥 加密 解密 完整代码如下 测试 测试程序 Python实现AES-CFB8加解密 Python实现AES算法 密码学课程老师留的作业,我觉得用python实现更简单,就用python写了一个加解密的程序.程序分成三个部分,一个部分是生成轮密钥,一个加密,一个是解密. 生成轮密钥 这个部分要看是不是四的倍数,非四的倍数是简单的,直接异或就可以了.如果是4的倍数,这时就需要执行字节代替和异或运算. for i in range(4):#把16进制转成十进

  • jQuery-1.9.1源码分析系列(十)事件系统之事件体系结构

    又是一个重磅功能点. 在分析源码之前分析一下体系结构,有助于源码理解.实际上在jQuery出现之前,Dean Edwards的跨浏览器AddEvent()设计做的已经比较优秀了:而且jQuery事件系统的设计思想也是基于该思想的,所以我们先分析一下Dean Edwards前辈的事件绑定. a. jQuery事件原型--Dean Edwards的跨浏览器AddEvent()设计 源码解读 //事件添加方法 function addEvent(element, type, handler) { //

  • Java编程中ArrayList源码分析

    之前看过一句话,说的特别好.有人问阅读源码有什么用?学习别人实现某个功能的设计思路,提高自己的编程水平. 是的,大家都实现一个功能,不同的人有不同的设计思路,有的人用一万行代码,有的人用五千行.有的人代码运行需要的几十秒,有的人只需要的几秒..下面进入正题了. 本文的主要内容: · 详细注释了ArrayList的实现,基于JDK 1.8 . ·迭代器SubList部分未详细解释,会放到其他源码解读里面.此处重点关注ArrayList本身实现. ·没有采用标准的注释,并适当调整了代码的缩进以方便介

  • Java并发系列之ReentrantLock源码分析

    在Java5.0之前,协调对共享对象的访问可以使用的机制只有synchronized和volatile.我们知道synchronized关键字实现了内置锁,而volatile关键字保证了多线程的内存可见性.在大多数情况下,这些机制都能很好地完成工作,但却无法实现一些更高级的功能,例如,无法中断一个正在等待获取锁的线程,无法实现限定时间的获取锁机制,无法实现非阻塞结构的加锁规则等.而这些更灵活的加锁机制通常都能够提供更好的活跃性或性能.因此,在Java5.0中增加了一种新的机制:Reentrant

  • Vue 源码分析之 Observer实现过程

    导语: 本文是对 Vue 官方文档深入响应式原理(https://cn.vuejs.org/v2/guide/reactivity.html)的理解,并通过源码还原实现过程. 响应式原理可分为两步,依赖收集的过程与触发-重新渲染的过程.依赖收集的过程,有三个很重要的类,分别是 Watcher.Dep.Observer.本文主要解读 Observer . 这篇文章讲解上篇文章没有覆盖到的 Observer 部分的内容,还是先看官网这张图: Observer 最主要的作用就是实现了上图中touch

  • Java并发 结合源码分析AQS原理

    前言: 如果说J.U.C包下的核心是什么?那我想答案只有一个就是AQS.那么AQS是什么呢?接下来让我们一起揭开AQS的神秘面纱 AQS是什么? AQS是AbstractQueuedSynchronizer的简称.为什么说它是核心呢?是因为它提供了一个基于FIFO的队列和state变量来构建锁和其他同步装置的基础框架.下面是其底层的数据结构. AQS的特点 1.其内使用Node实现FIFO(FirstInFirstOut)队列.可用于构建锁或者其他同步装置的基础框架 2.且利用了一个int类表示

  • JDK动态代理步骤详解(源码分析)

    动态代理步骤 1.创建一个实现接口InvocationHandler的类,它必须实现invoke方法 2.创建被代理的类以及接口 3.通过Proxy的静态方法 通过Proxy的静态方法 ProxyObject proxyObject = new ProxyObject(); InvocationHandler invocationHandler = new DynamicProxy(proxyObject); ClassLoader classLoader = proxyObject.getCl

  • Java Shutdown Hook场景使用及源码分析

    目录 背景 Shutdown Hook 介绍 关闭钩子被调用场景 注意事项 实践 Shutdown Hook 在 Spring 中的运用 背景 如果想在 Java 进程退出时,包括正常和异常退出,做一些额外处理工作,例如资源清理,对象销毁,内存数据持久化到磁盘,等待线程池处理完所有任务等等.特别是进程异常挂掉的情况,如果一些重要状态没及时保留下来,或线程池的任务没被处理完,有可能会造成严重问题.那该怎么办呢? Java 中的 Shutdown Hook 提供了比较好的方案.我们可以通过 Java

随机推荐