1

1. 方法区和元空间

方法区是《Java虚拟机规范》中规定的一个内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

方法区是一个规范,它的实现取决于不同的虚拟机。

在Java8之前,HotSpot虚拟机使用永久代来实现方法区。
而Java8之后,HotSpot虚拟机使用元空间来实现方法区。

(1) 方法区和元空间的内存模型

虽然在逻辑上,包含新生代和老年代的堆区和包含永久代的方法区是两个不同的区域。
但是在物理存储上,老年代和永久代却是连续的一块内存。
image.png

元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存

不过,虽然元空间继承了永久代,但并不是完全存储了方法区中所有的内容。
元空间存储了类的元数据信息(包括类的名称、方法信息、字段信息以及class文件等),而静态变量、字符串常量池、运行时常量池等被存储在堆中。

image.png

(2) 元空间的组成结构

元空间由两大部分组成:Klass Metaspace和NoKlass Metaspace。

1) Klass Metaspace

Klass Metaspace就是用来存放klass的。
Klass是class文件在JVM里的运行时的数据结构,实际上就是个C++对象。(它与Class对象不同,Class对象是类的对象,是java.lang.Class的一个实例,存储在堆里,不要把这两个搞混了。)

Klass Metaspace的大小可以通过-XX:CompressedClassSpaceSize参数来控制,默认大小为1G。

但是这块内存不是必须存在的,当开启压缩指针时或者把-Xmx设置大于32G的话,就不会有这块内存。这种情况下Klass都会存在NoKlass Metaspace里。

2) NoKlass Metaspace

NoKlass Metaspace专门来存klass相关的其他的内容,比如方法信息等。这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。

Klass Metaspace和NoKlass Mestaspace是所有classloader共享的。每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。

(3) 元空间的内存管理

image.png
元空间的内存管理由元空间虚拟机来完成。先前,对于类的元数据我们需要不同的垃圾回收器进行处理,现在只需要执行元空间虚拟机的C++代码即可完成。

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

而当Metaspace VM发现某个类加载器不再存活了,会把对应的空间整个回收。

(4) 为什么使用元空间代替永久代?

1) 解决永久代OOM的问题

在Java 8之前,经常会遇上java.lang.OutOfMemoryError: PermGenspace的问题。
因为方法区主要存储类的相关信息,所以对于动态生成类的情况比较容易出现永久代的内存溢出。

永久代的大小是在启动时固定好的,通常使用PermSize和MaxPermSize设置永久代的初始内存和上限,很难进行调优,如果使用默认值很容易遇到OOM错误。

而元空间并不在虚拟机中,而是使用本地内存,因此在理论上是可以无限使用本地内存的,意味着只要本地内存足够,就不会出现OOM的问题。

但是,为了更好的控制元空间,JVM同样提供了参数来限制它的使用:

-XX:MetaspaceSize,class metadata的初始空间配额,以bytes为单位,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当的降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize(如果设置了的话),适当的提高该值。它的默认大小在不同的平台上是不同的,比如在64位的Windows下,默认大小是21M。

-XX:MaxMetaspaceSize,可以为class metadata分配的最大空间。默认为-1,即没有限制。

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为class metadata分配空间导致的垃圾收集。

-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为class metadata释放空间导致的垃圾收集。

2) 简化GC

永久代使用Full GC来进行垃圾回收,这会为 GC 带来不必要的复杂度,并且回收效率偏低。

而元空间有着自己的内存管理技巧,从而省掉了GC扫描及压缩的时间:

Metaspace VM使用一个块分配器(chunking allocator)来管理Metaspace空间的内存分配。块的大小依赖于类加载器的类型。
Metaspace VM中有一个全局的可使用的块列表(a global free list of chunks)。当类加载器需要一个块的时候,类加载器从全局块列表中取出一个块,添加到它自己维护的块列表中。当类加载器死亡,它的块将会被释放,归还给全局的块列表。
块(chunk)会进一步被划分成blocks,每个block存储一个元数据单元(a unit of metadata)。Chunk中Blocks的是分配线性的(pointer bump)。这些chunks被分配在内存映射空间(memory mapped(mmapped) spaces)之外。在一个全局的虚拟内存映射空间(global virtual mmapped spaces)的链表,当任何虚拟空间变为空时,就将该虚拟空间归还回操作系统。

3) 合并HotSpot和JRockit

JRockit是Oracle虚拟机,它是没有永久代的概念的。
合并HotSpot和JRockit并不奇怪,毕竟Oracle已经收购了Sun了。

参考引用

方法区:https://segmentfault.com/a/11...
元空间:https://www.jianshu.com/p/a6f...
Metaspace整体介绍:https://www.cnblogs.com/duanx...
一文读懂 - 元空间和永久代:https://juejin.cn/post/684490...
警惕动态代理导致的Metaspace内存泄漏问题:https://blog.csdn.net/xyghehe...
Metaspace Research:https://gist.github.com/erikd...
永久代(PermGen)和元空间(Metaspace):https://blog.csdn.net/laomumu...
关于 MetaSpace 及 FastJSON 导致的 OOM:https://www.jianshu.com/p/830...


Zealf
13 声望1 粉丝