MetaSpace

什么是MetaSpace?

openjdk使用Metaspace来存储class的元数据,它在java 虚拟机进程中占了很对一块非java堆内存

注意: JDK版本依赖,元空间自jdk8以来发生了很多变化,当本文没有明确表达的时候我们讨论的JDK11.

class元数据是jvm进程java classes在运行时期的一个描述,根本上来说,JVM处理 java class的任何信息,包括但不限于来自JVM class file format

例如:

  • Klass 结构-java class在虚拟机内部的运行时状态表示,这包含了vtable和itable
  • Method元数据- 类文件中method_info 的运行时等价物,包含了字节码,异常表,常量等
  • 常量池
  • 注解(Annotations)
  • 在运行时作为JIT决策的基础的方法计数器收集
  • 等等

注意: 我们将会在后面的博客中讨论这些细节,英语比较好的的大佬可以直接去听油管的Class MetaData: A User Guide

即使java.lang.Class是一个运行在Java 堆里面的对象,但是class元数据它本身不是一个java对象,也不运行在Java堆,他们运行在Java堆外的原生内存区域,这个区域呗叫做MetaSpace.

MetaSpace 什么时候分配?

来自MetaSpace的分配和类加载是一起的。

当一个类被加载和在JVM里它的运行时表示被准备时,MetaSpace 被它的类加载器分配用来存储这个类的元数据,如下图

什么时候 MetaSpace释放?

一个class被分配的元空间时被它的类加载器所拥有,它只有当这个类加载器自己被卸载的时候才会被释放,不是之前。

只有当这个class loader 加载的所有的类没有或者的实例时,没有指向所加载的类和 class loader,而且GC也运行了 MetaSapce才会释放。(参考:JLS 12.7. Unloading of Classes and Interfaces)

内存经常被保留!

然而,释放MetaSpace并不一定意味着被释放的内存就会归还给操作系统。
所有的或者一部分内存还是会被保留在JVM,这部分内存对于未来的类加载而被重用,但是这时也就意味着没有使用的那部分内存在JVM进程里。

这部分被保留的没有使用的内存有多大取决于MetaSpace的分裂程度-元空间的使用部分和空闲部分的紧密程度。另外,一部分MetaSpace( Compressed Class Space)将根本不回归还给操作系统。

注意:详细的释放逻辑将会在下一片文章讲解
注意:关注到MetaSpace的占用时一个升级到JDK11的很好的理由, JDK11在这方面有很多的改进,包括减少碎片化和内存浪费。

MaxMetaspaceSize, CompressedClassSpaceSize

这两个参数时用来限制MetaSpace的大小的:

  • -XX:MaxMetaspaceSize 决定了MetaSpace被允许增长到的最大commit大小,默认时不做限制的。
  • -XX:CompressedClassSpaceSize 决定了MetaSpace很重要的一部分virtual的大小,Compressed Class Space,它默认的值时1G,(注意:reserved space, not committed).
    关于MetaSapce的大小的细节后面会讲

MetaSpace 和 GC

只有当GC运行和class loader 卸载时MetaSpace才被释放,所以在某种程度上来说,运行GC来清理旧的class 元数据时有意义的,即使在常规的JAVA堆中运行GC没有获得太大的收益,会有两种情况触发元空间诱导的GC:

  • 正在分配元空间: VM拥有一个阀值,通过触发一次GC,他没有首先尝试去收集老的类加载器,而是重用它的MetaSpace,MetaSpace就不会涨,这就是MetaSpace的GC阀值,这能保证MetaSpace不会增长即使没有释放老的元数据,这个阀值会上下移动,大致和commited内存保持一定的范围

  • 当遇到一次MetaSpace OOM -这个OOM发生在当commited的内存总和达到了MaxMetaSpaceSize时,或者运行超过了Compressed Class Space,GC企图纠正这种情况, 如果它实际上很好的卸载了class loader, 否则即使即使我们有足够的JAVA堆,仍然会进入糟糕的GC循环

参考:

  • https://stuefe.de/posts/metaspace/what-is-metaspace/
  • https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.15
  • https://www.cnblogs.com/mazhimazhi/p/13456515.html

MetaSpace 架构

我们继续探究MetaSpace的架构。

像大多数的重要的分配器一样,MetaSpace也是分层实现的。

在底层,很大的区域内存被分配的内存来自操作系统 , 在中间层我们切开哪些区域为一些不这么大的chunks并且把他们交给 class loaders。

在最上层,class loaders又将这些chunks切开来服务调用者代码块。
下面我们来根据不通的层级来了解下MetaSpace的细节

下层: 空间列表(The space list)

在最底层-在颗粒度最粗-被MetaSpace保留的和通过像mmap(3)的虚拟内存调用按需向操作系统申请内存,在64位平台每次是2MB的区域。

那些映射的区域被作为节点保存在一个名为VirtualSpaceList的全局链表里。
每个节点管理一个高水位,把以提交的空间和未提交的空间分离开。当内存分配到了一个高水位的时候一个新的内存页就会即刻提交。(保留一点余量是为了避免频繁的调用操作系统)??这句话没理解

这个过程会一直持续到这个节点被完全使用完,然后一个新的节点被分配并且加到VirtualSpaceList这个链表里,然后这个老的节点开始“退休”(退休节点留下内存空间并没有小时,但是被分割成快加到全局 freelist里)

内存被分配从一个节点到叫做MetaChunk的chunks,这些chunks有三种大小分别叫特殊的,小的和中等的,命名是有历史意义的,通常这三种对应的大小是1K/4K/64K(这个大小32位平台有所不同,以及这些chunk是位于CompressedClassSpace 或者 Non-Class Metaspace也不同)
模型如下:

VirtualSpaceList 和它的节点们是全局的结构体,但是 Metachunk 确实是被一个class loader所持有,所以在VirtualSpaceList中一个单节点也可能经常包含来自不同class loader的chunks
如下图:

当一个class loader和它相关联的class被卸载,MetaSpace过去持有的它的class metadata被释放。现在所有的空白chunks被加到全局 free list里(ChunkManager)
如下图:

这些chunks被重用的情况: 如果另外一个class loader 开始加载别的class并且分配MetaSpace,这种情况可能会使用一个chunks来替代分配一个新的chunks:
如下图:

中间层: MetaChunk

一个class loader 从MetaSpace请求一块内存用来存储MetaData(通常情况下很小-几十或者几百字节),他将获得一块MetaChunk代替–通常是比他请求的大得多的内存。

为什么?因为从全局的VirtualSpaceList 分配内存是非常昂贵的。VirtualSpaceList是一个全局的结构器而且需要加锁,我们不想这样做,所以一块更大的内存被分给了calssloader–那就是MetaChunk,loader会使用它来满足更快的未来的内存分配,和别的class loader并发使用,而且没有锁。只有当那个chunk被用完了class loader才会再一次干扰VirtualSpaceList

MetaSapce 分配器怎样决定多大内存交给class loader?就是一个猜测:

  • 一个新的标准的应用的class loader, 会拿到4K大小的chunks,直到使用到达4这个阀值是,MetaSpace 分配器会给这个laoder更大的64K的chunks。
  • bootstrap class loader(引导类加载器)倾向加载很多类,所以分配器给它了一个很大的chunk,从一开始起就是4M, 当然这是可用通过InitialBootClassLoaderMetaspaceSize来调节的。
  • Reflection class loaders (jdk.internal.reflect.DelegatingClassLoader)(反射类加载器)和匿名类加载器(不是真正的类加载器,但是现在来时这不重要),每次只加载一个class,所以一开始的时候他们只是被给了1K大小的chunks,因为假定他们会很快停止使用MetaSpace,所以再给他们任何东西都会被浪费。

注意这整个优化-- 在假定加载器很快就会需要的情况下,为它提供比当前需要更多的空间这种行为是一个对未来内存分配行为的赌注,而且这可能正确也可能不正确,当分配器给了他们一个很大的chunk的时候可能会直接停止加载。

上层结构: MetaBlock

在一个MetaChunk 中我们还有第二中分配器- 本地类加载器分配器,它把MetaChunk 划分为小的配额单元,这些单元被叫做MetaBlock,并且是实际分发给调用者的单元 (所以对于 实例来说一个MetaBlock拥有一个InstanceKlass)

这个本地类加载器分配器可以是简单的因此很快:

class metadata的生命周期被绑定到了class loader,class loader 死亡的时候 class metadata可以被大批量释放,所以JVM不需要关注随机释放的Metablocks

让我们来看看MetaChunk

当MetaChunk 产生的时候,它只包含了头部,后面的分配只被分配到顶部,??,而且分配器不需要太智能,因为它可以依赖整个Metadata被批量释放

注意当前chunk的“unused”部分:一旦这个chunk被class loader所持有,这部分未使用的区域只能被相同的class loader所使用,如果loader停止加载class,这部分空间实际上就被浪费掉了。

ClassloaderData和ClassLoaderMetaspace

class loader 把它的原生表示维护在一个叫ClassLoaderData的原生结构里。

ClassLoaderData 结构体持有了一个ClassLoaderMetaspace结构体的引用,这个ClassLoaderMetaspace是用来维护当前这个class loader正在使用的所有的MetaChunks的列表。

当一个loader被卸载的时候,这个loader相关联的ClassLoaderDataClassLoaderMetaspace会被删除,这会释放所有的这个class loader使用的chunks到MetaSpace FreeList里面去,如果条件正确,那么这些空闲的内存有可能会归还给操作系统也可能不会,详情如下。

匿名类(Anonymous classes)

ClassloaderData != ClassLoaderMetaspace

注意我们一直在说“Metaspace 内存被它的class loader所持有”,但是这里我们撒了点谎,这只是一种简化的说法,随着匿名类的加入这变得更加的复杂。

这里是一些被生成用来支撑动态语言特性的结构。当一个加载器加载一个匿名类,这个类它拥有独立的ClassLaoderData它的生命周期和匿名类一样,而不是这个类的class loader外壳(所以它相关的metadata可以在class loader外壳被收集之前收集),那也意味着一个class loader 有一个主要的加载所有普通类的ClassLoaderData,还有一个次要的针对每个匿名类的结构。

这中和别的东西分离的目的是为了避免不必要地延长lambda和Method句柄等metspace分配的生命周期。

结构如下图:

所以最后再问一遍什么时候MetaSpace的内存会归还给操作系统啊?

再看这个问题,我们可以更详细的回答这个问题了:

当一个VirtualSpaceListNode内所有的chunks是空的,那个节点自身被删除。这节点会从VirtualSpaceList被移除,这节点的空闲的chunks将会从MetaSapce freelist移除,这节点变成未映射的节点,那么它的内存将会归还给操作系统, 这个节点被清除

如下图:

一个节点的所有chunks是空的,那么class loader 所拥有的所有chunks必须是已经死亡了。

下面的情况是和会发生取决于碎片化程度:

一个节点大小为2MB;chunks的大小从1K-64K不等;通常的负载是每个节点~150 - 200个chunks。如果这些chunks都已由单独的class loader分配,那么收集该class loader将释放节点并将其内存释放给操作系统。

但是,如果这些chunks由不同的class loader拥有,并且有不同的生命周期,则不会释放任何内容。当我们处理许多小型class loader时(例如,用于匿名类或反射委托的class loader)可能会出现这种情况。

另外,请注意,部分metspace(Compressed Class Space)将永远不会被释放回操作系统。

回顾总结:

  • 从操作系统中获取来的被保留的2MB大小的内存区域维护在一个全局的链表中。这样的空间是按需提交的。
  • 这些空间被分割为chunk,然后交给class loader,一个chunk属于一个class loader。
  • chunk被进一步分割为更小的 blocks,这些是交给调用者的分配单元。
  • 当一个class loader消亡,它所拥有的chunks被添加到全局空闲列表中并被重用。部分内存可能会被释放会操作系统,但是这取决于内存的碎片化程度和运气。

备注:鉴于我这个菜鸡英语和语文水品有限有些地方有可能翻译不准确,还望大佬一起讨论

参考:

  • https://stuefe.de/posts/metaspace/metaspace-architecture/
  • https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/hotspot/share/memory/metaspace

MetaSpace简介相关推荐

  1. Metaspace GC 问题排查

    这里写目录标题 背景描述 问题排查 环境信息 GC日志分析 Arthas 使用简介 下载 arthas 进入arthas 查看加载最多的类 获取所有类信息 查看加载最多的package 查看packa ...

  2. Java虚拟机JVM简介与理解(三)

    Java虚拟机JVM简介与理解(三) 问题背景 PC程序计数器 虚拟机栈 本地方法栈 堆 元空间 方法区 运行时常量池 直接内存 Lyric: 彻底把我囚禁在你的呼吸 问题背景 Java虚拟机JVM简 ...

  3. 再谈GC1:GC简介,分代与回收算法

    说明:在本文中, Garbage Collection 翻译为 "垃圾收集", garbage collector 翻译为 "垃圾收集器";一般认为, 垃圾回收 ...

  4. JobManager 内存简介

    一.简介 JobManager 具有许多与协调 Flink 应用程序的分布式执行有关的职责:它决定何时调度下一个 task(或一组 task).对完成的 task 或执行失败做出反应.协调 check ...

  5. Flink(九):JobManager 内存简介

    一.简介 JobManager 具有许多与协调 Flink 应用程序的分布式执行有关的职责:它决定何时调度下一个 task(或一组 task).对完成的 task 或执行失败做出反应.协调 check ...

  6. Flink(十):TaskManager 内存简介

    一.简介 Flink  TaskManager(也称为 worker)执行作业流的 task,并且缓存和交换数据流,TaskManager 负责执行用户代码.根据实际需求为 TaskManager 配 ...

  7. flink从入门到精通-flink简介

    文章目录 flink简介 名称的由来 什么是flink 为什么需要flink 流式计算框架比较 模型 Streaming Model API 形式 保证机制 容错机制 状态管理 flink基本概念 f ...

  8. JVM原理系列--元空间(MetaSpace)与永久代(PermGen)的区别

    原文网址:JVM原理系列--元空间(MetaSpace)与永久代(PermGen)的区别_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍JVM中元空间(MetaSpace)与永久代(PermG ...

  9. java8 metaspacesize_Metaspace 之三--jdk8 Metaspace 调优

    简介 jdk8的元空间的初始大小是21M,如果启动后GC过于频繁,请将该值设置得大一些. 如果应用启动时,FGC出现了,可能是由于metaspace导致,例如: 从JDK8开始,永久代(PermGen ...

最新文章

  1. python基础代码事例-零基础学习Python开发练习100题实例(2)
  2. 用gojs写的流程图demo
  3. SiameseSentenceSimilarity相似句子匹配分类项目
  4. 回溯法 —— 求解子集和问题
  5. asp.net 抓取html内容,c# – 如何从ASP.NET获取网页的HTML内容
  6. 目标跟踪 | 目标跟踪算法总结
  7. 使用plsql登陆oracle数据库,使用PLSQL 创建Oracle数据库用户
  8. 计算机无本地安全策略,如何打开本地安全策略、如何解决“未授予用户在此计算机上的请求登录类型”...
  9. 淘宝APP用户行为分析
  10. SDL入门教程(七):SDL抠色(Color Keying)
  11. oracle和mysql查询条件排序_Oracle数据库中ORDERBY排序和查询按IN条件的顺序输出
  12. 服务器只读团体字信息,服务器团体名配置
  13. 利用 matplotlib 制作条形图
  14. [刷机教程] [Root] S-OFF的同学来Root你的HTC Desire S
  15. 不知道自己不知道 知道自己不知道 不知道自己知道 知道自己知道
  16. 灯光篇之一【环境光AmbientLight】
  17. IINA 1.1.0beta1中文版 - Mac最强万能视频播放器
  18. 数字图像处理_冈萨雷斯_M函数编程简介
  19. 金蝶专业版12.2账套被加密解决方案
  20. 一文详解java线程池 详解Java线程池的七个参数 详解池化技术 java如何选择核心线程数 详解Java线程池的拒绝策略

热门文章

  1. 腾讯云轻量应用服务器搭建lsky图床并使用KODO云存储
  2. java hook技术_API Hook基本原理和实现 - - JavaEye技术网站
  3. sharepoint 2016 学习系列篇(15)-自定义列表应用篇-(4)数据权限配置
  4. mapbox-gl开发教程(三):在线影像底图加载
  5. linux 跳板机脚本,跳板机脚本(粗糙版)
  6. PVE 安装群晖转换img镜像引导
  7. FlyAI资讯:人工智能的前世今生
  8. Linux TC-prio 流量分类实例
  9. html创建文字,Three中创建文字的几种方法
  10. git未提交代码找回