JVM 加载类的时候,需要记录类的元数据,这些数据会保存在一个单独的内存区域内,在 Java 7 里,这个空间被称为 永久代(Permgen),在 Java 8 里,使用 元空间(Metaspace)代替了永久代。永久代和元空间保存的数据并不完全一样,永久代中还保存另一些与类的元数据无关的杂项。
如我们之前的一篇文章 016:字符串对象在 JVM 中是如何存放的中说的,在 Java 7 里将字符串常量从永久代移动到了堆区域,但是永久代并没有完全改造完成。直到 Java 8,永久代的改造才算完全搞定,在元空间中保存的数据比永久代中纯粹很多,就是类的元数据,这些信息只对编译期或 JVM 的运行时有用。
理论学习
使用 Java 8 以后,关于元空间的 JVM 参数有两个:-XX:MetaspaceSize=N
和-XX:MaxMetaspaceSize=N
,对于 64 位 JVM 来说,元空间的默认初始大小是 20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize 用于设置 metaspace 区域的最大值,这个值可以通过 mxbean 中的 MemoryPoolBean 获取到,如果这个参数没有设置,那么就是通过 mxbean 拿到的最大值是 -1,表示无穷大。
由于调整元空间的大小需要 Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量 Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在 JVM 参数中将 MetaspaceSize 和 MaxMetaspaceSize 设置成一样的值,并设置得比初始值要大,对于 8G 物理内存的机器来说,一般我会将这两个值都设置为 256M(PS:读者可以根据自己的实际情况再调整)。
源码分析
MetaspaceSize 表示 metaspace 首次使用不够而触发 FGC 的阈值,只对触发起作用,原因是:垃圾搜集器内部是根据变量 _capacity_until_GC
来判断 metaspace 区域是否达到阈值的,初始化代码如下所示:
void MetaspaceGC::initialize() {
// Set the high-water mark to MaxMetapaceSize during VM initializaton since
// we can't do a GC during initialization.
_capacity_until_GC = MaxMetaspaceSize;
}
GC 收集器会在发生对 metaspace 的回收会,会计算新的_capacity_until_GC 值,以后发生 FGC 就跟 MetaspaceSize 没有关系了。
如果不设置 MetaspaceSize,则默认的_capacity_until_GC 为 20M 左右,具体代码如下:
本号专注于后端技术、JVM 问题排查和优化、Java 面试题、个人成长和自我管理等主题,为读者提供一线开发者的工作和成长经验,期待你能在这里有所收获。