笔者近期在面试的时候被问到了这个问题,元空间也是Java8当时的一大重大革新,之前暑期实习求职的时候有专门看过,但是近期秋招的时候JVM相关的内容确实有点生疏了,故在此进行回顾。
结构
首先,我们应了解JVM的堆结构,主要有两个版本及Java7以及Java8。
元空间的前身-永久代(Permanent Generation)
JAVA永久代的演化
- JDK7开始,字符串常量和符号引用等就被移出永久代,字符串字面量迁移至Java堆 /符号引用转移到了native heap。
- JDK8,永久代被彻底地移出了JVM,取而代之的是元空间MetaSpace,把类的元数据放到本地化的堆内存native heap中,这块区域就叫Metaspace。
JDK 1.7 为什么要将字符串常量池移动到堆中?
主要是因为永久代(方法区实现)的 GC 回收效率太低,只有在整堆收集 (Full GC)的时候才会被执行 GC。Java 程序中通常会有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。
作用
JVM的MetaSpace存储JVM的元信息,而且是在堆外存储。主要涉及以下内容:
- JVM中类的元数据在Java堆中的存储区域
- Java类对应的HotSpot虚拟机中的内部表示也存储在这里
- 类的层级信息,字段,名字
- 方法的编译信息及字节码
- 变量
- 常量池和符号解析
注意到,堆外内存就是把内存对象分配在堆(新生代+老年代+永久代)以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
理论上以上信息占用的内存空间在服务启动后就会比较稳定,并不会出现上文中提到的一直增长的,甚至导致OOM的情况出现。
回到正题,元空间的引入是为了替代原来使用的 PermGen 区域,PermGen 区域主要用于存储永久代(Permanent Generation)中的类信息和常量池信息。随着Java应用程序对内存需求的不断增加,PermGen 区域经常会出现内存泄漏和 OutOfMemoryError 的问题,因此引入了元空间来解决这些问题。
元空间的大小可以通过参数配置的方式指定。在启动JVM时,可以使用以下参数来指定元空间的大小:
-XX:MaxMetaspaceSize:指定元空间的最大大小,单位是字节。
-XX:MinMetaspaceSize:指定元空间的最小大小,单位是字节。
-XX:InitialMetaspaceSize:指定元空间的初始大小,单位是字节。
这些参数可以通过命令行或配置文件的方式传递给JVM。在默认情况下,元空间的大小是动态调整的,根据应用程序的需要自动扩展或缩小。当元空间快要达到最大或最小大小时,JVM会尝试进行垃圾回收来释放不再使用的类信息和常量池信息。
参考资料
JVM Metaspace OOM的排障以及原理分析 - 掘金