深入JVM元空间以及弹性伸缩机制

6 篇文章 0 订阅

个人博客

深入JVM元空间以及弹性伸缩机制 | iwts’s blog

JVM内存模型中元空间所在位置

即在JVM运行时的内存模型。总体上有这样的图:

元空间

上面的图其实有点不太准。方法区本质上只是JVM的一个标准,不同JVM在不同版本下都可能有不同的实现,例如JDK1.7之前的永久代之类的。

这里主要聊JDK1.8下HotSpot的实现:元空间。

元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小:

  1. -XX:MetaspaceSize,初始空间大小,达到该值就会触发GC进行类型卸载,同时JVM会对该值进行调整。

    a> 如果释放了大量的空间,就适当降低该值。

    b> 如果释放了很少的空间,那么在不超过MaxMetaspaceSize的前提下,适当提高该值。

  2. -XX:MaxMetaspaceSize,最大空间大小,默认是没有限制的。

元空间也有溢出,超过了最大空间则会OOM,但是会明显提示:

Metaspace OutOfMemoryError

但是相比而言,因为用的本地内存,所以总归还是大很多的,只要最大空间设的足够大,OOM几率要比1.7之前的永久代低很多。

元空间是放在堆外内存,所有JVM线程间共享,它存储每一个类的结构。其中包括:

  1. 运行时常量池。
  2. 字段和方法数据。
  3. 方法和构造函数的代码。
  4. 还有特殊的方法用于类和实例的初始化,以及接口的初始化。
  5. JIT代码缓存。

元空间两大组成

Metaspace由两大部分组成:

  1. Klass Metaspace。
  2. NoKlass Metaspace。

klass其实就是class的意思,区分一下概念。

Klass Metaspace

Klass本质上是class文件在JVM里的运行时数据结构,但是Class的实例对象是存在堆里的,两者没有关系。

而Klass存储在压缩类空间中。

压缩类空间 Compressed Class Pointer Space

压缩类空间(Compressed Class Pointer Space),是一块连续的内存区域,紧接着堆。通过-XX:CompressedClassSpaceSize来控制这块内存的大小,默认是1G。

NoKlass Metaspace

就是相反,只要不是压缩类空间,但是在元空间中的,就都是NoKlass Metaspace。

专门来存class相关的其他的内容,比如方法数据,运行时常量池等,可以由多块不连续的内存组成。

但是也可以存class,如果压缩类空间不存在,那么class就存在这里。

运行时常量池

类或接口的字节码文件里的常量池的运行时表示形式,它包含几种常量。例如:

  1. 编译时就已经知道的数字字面量值。
  2. 必须在运行时解析的方法和字段的引用。

运行时常量池的功能类似于传统语言的符号表,不过它包含的数据会更加宽泛。

运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class类都有一个运行时常量池,而被该类全部的实例对象共用。类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

例如static变量、方法,都是在运行时常量池中。

运行时常量池分配在JVM的NoKlass Metaspace,类或接口的运行时常量池在类或接口被JVM创建时才会构建。

元空间内存管理

在元空间中,类和其元数据的生命周期与其对应的类加载器相同,只要类的类加载器是存活的,在Metaspace中的类元数据也是存活的,不能被回收。

每个加载器有单独的存储空间,通过 ClassLoaderMetaspace来进行管理 SpaceManager* 的指针,相互隔离的。

所以GC本身是不处理元空间的,因为只要类加载器没有问题,里面的数据必然在其生命周期内,就算GC也不能认为是垃圾。

但是可以执行卸载,直接卸载类加载器。如果某个类加载器不再存活,那么会卸载整个类加载器下的元空间数据。

Metaspace VM利用块分配器(Chunking Allocator)来管理元空间的内存分配。块的大小依赖于类加载器的类型。

Metaspace VM中有一个全局的可使用的块列表(A Global Free List of Chunks)。当类加载器需要一个块的时候,类加载器从全局块列表中取出一个块,添加到它自己维护的块列表中。当类加载器死亡,它的块将会被释放,归还给全局的块列表。

元空间弹性伸缩

由于元空间和堆并不在一起,所以这块的空间可以不用设置或者单独设置。但是一般情况下,为了避免元空间耗尽 JVM 的内存,所以都会设置 MaxMetaSpaceSize,即最大元空间大小。

在运行过程中,如果实际大小小于这个值,JVM 就会通过 -XX:MinMetaspaceFreeRatio-XX:MaxMetaspaceFreeRatio两个参数动态控制整个 MetaSpace 的大小。

这个动态控制的过程就是弹性伸缩。

但是弹性伸缩执行的时候性能很差,为了避免弹性伸缩带来的额外 GC 消耗,一般情况下是保证元空间大小不变,也就是将-XX:MetaSpaceSize-XX:MaxMetaSpaceSize两个值设置为固定。

但是这样也会导致在空间不够的时候无法扩容,然后频繁地触发 GC,最终 OOM。

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值