Java8 MetaSpace介绍

前面 我们学习了Java7的内存模型,重点了解了它的Runtime Data Area,今天我们要来学习一下Java8 HotSpot(TM) VM内存模型,看看它与Java7 VM存在哪些差异。

与Java7相比较,Java8 VM的一个重大更新:完全移除永久代(PermGen),取而代之的为元空间(Metaspace)

Java8 VM MetaSpace

为什么要移除PermGen

来源:http://openjdk.java.net/jeps/122

Jon Masamitsu 对此的解释如下:

A goal for removing perm gen was so that users do not have to think about correctly sizing it.

Set MetaspaceSize to a value larger than the default, if you know that your applications needs more space for class data. Setting it to a larger size will avoid some number of GC’s at startup. It is not necessary and I do not particularly recommend it unless you want to avoid as many GC’s as possible.

Set MaxMetaspaceSize if you want to limit the space for class data. You might want to do this if you suspect you are leaking classloaders and want the application to stop before it uses up too much native memory. Another case might be where you have multiple applications running on a server and you want to limit how much space each uses for class data.

PermGen空间的大小在JVM启动时就已经分配好了,但是随着动态类加载的情况越来越多,如果设置的太小,则容易出现OOM的异常,设置的太大,则容易造成空间浪费。

而Metaspace空间的容量则会根据应用程序在运行时的需求动态调整大小,省得为PermGen的大小分配操碎了心。

有哪些变化

来源:http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-September/006679.html

PermGen 情况
  • PermGen 被完全移除,对应的控制参数 PermSizeMaxPermSize 也变得无效。
Metaspace内存分配模型
  • 类的元数据信息(metadata)不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。PermGen中剩下的一些杂项数据已移至Java Heap中。

  • 用于描述类元数据的类(klasses)已被删除(klassKlass及其派生类)。

Metaspace空间容量
  • 默认情况下,类元数据分配受到可用的本机内存总容量的限制(容量依然取决于你使用32位JVM还是64位操作系统的虚拟内存的可用性)。
  • 可以使用一个新的参数 (MaxMetaspaceSize)来限制用于类元数据的本地内存。如果没有特别指定,元空间将会根据应用程序在运行时的需求动态调整大小。
Metaspace垃圾回收
  • 当class metadata的使用的内存达到MetaspaceSize(32位clientVM默认12Mbytes,32位ServerVM默认是16Mbytes)时就会对死亡的类加载器和类进行垃圾收集。 设置MetaspaceSize为一个较高的值可以推迟垃圾收集的发生。
  • 为了限制垃圾回收的频率和延迟,适当的监控和调优元空间是非常有必要的。元空间过多的垃圾收集可能表示类加载器内存泄漏或对你的应用程序来说空间太小了。
Java Heap影响
  • 一些杂乱的数据从PermGen移到了Java Heap,这意味着升级到JDK8之后,Java Heap的大小会有明显的增加。

Metaspace的组成

来源:http://lovestblog.cn/blog/2016/10/29/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的阈值比较远的时候,调整会比较明显

MetaSpace 与 PermGen 运行时比较

前面提到过,MetaSpace与PermGen最主要的区别就在于,内存空间的是否能够自动扩容上,下面我们来分别演示一下MetaSpace与PermGen的GC的表现形式之间的区别。示例代码 下载

JDK 1.7 @64-bit – PermGen depletion

演示异常:ERROR: java.lang.OutOfMemoryError: PermGen space

VM 参数:

1
-verbose:gc -Xms1024M -Xmx1024M -XX:MaxPermSize=128m -XX:PermSize=128m -XX:+PrintGCDetails -Xloggc:~/Desktop/gc.log

JVisualVM监控显示:

Java8_MetaSpace_1

GC日志:

gc_1

JDK 1.8 @64-bit – Metaspace dynamic re-size

演示 metaspace 空间不断动态调整的过程

VM 参数

1
-verbose:gc -Xms1024M -Xmx1024M -XX:+PrintGCDetails -Xloggc:~/Desktop/gc.log

JVisualVM监控显示:

JVisualVM_2

GC日志:

gc_2

JDK 1.8 @64-bit – Metaspace depletion

演示异常:ERROR: java.lang.OutOfMemoryError: Metadata space

VM 参数:

1
-verbose:gc -Xms1024M -Xmx1024M -XX:MaxMetaspaceSize=128m -XX:+PrintGCDetails -Xloggc:~/Desktop/gc.log

JVisualVM监控显示:

JVisualVM_3

GC日志:

gc_3

参考资料

请我喝杯咖啡吧~