个人博客
深入JVM元空间以及弹性伸缩机制 | iwts’s blog
JVM内存模型中元空间所在位置
即在JVM运行时的内存模型。总体上有这样的图:
元空间
上面的图其实有点不太准。方法区本质上只是JVM的一个标准,不同JVM在不同版本下都可能有不同的实现,例如JDK1.7之前的永久代之类的。
这里主要聊JDK1.8下HotSpot的实现:元空间。
元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过参数来指定元空间的大小:
-
-XX:MetaspaceSize
,初始空间大小,达到该值就会触发GC进行类型卸载,同时JVM会对该值进行调整。a> 如果释放了大量的空间,就适当降低该值。
b> 如果释放了很少的空间,那么在不超过
MaxMetaspaceSize
的前提下,适当提高该值。 -
-XX:MaxMetaspaceSize
,最大空间大小,默认是没有限制的。
元空间也有溢出,超过了最大空间则会OOM,但是会明显提示:
Metaspace OutOfMemoryError
但是相比而言,因为用的本地内存,所以总归还是大很多的,只要最大空间设的足够大,OOM几率要比1.7之前的永久代低很多。
元空间是放在堆外内存,所有JVM线程间共享,它存储每一个类的结构。其中包括:
- 运行时常量池。
- 字段和方法数据。
- 方法和构造函数的代码。
- 还有特殊的方法用于类和实例的初始化,以及接口的初始化。
- JIT代码缓存。
元空间两大组成
Metaspace由两大部分组成:
- Klass Metaspace。
- 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就存在这里。
运行时常量池
类或接口的字节码文件里的常量池的运行时表示形式,它包含几种常量。例如:
- 编译时就已经知道的数字字面量值。
- 必须在运行时解析的方法和字段的引用。
运行时常量池的功能类似于传统语言的符号表,不过它包含的数据会更加宽泛。
运行时常量池是在类加载完成之后,将每个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。