类加载器不直接将.class文件塞入元空间,而是通过“读取→解析→构建→封装”四步流程:先以io方式获取byte[]字节流(来自文件、jar、网络或动态生成),再由jvm校验格式、构建元空间运行时结构、暂存符号引用,最后在堆中创建class对象作为访问元数据的入口。

类加载器并不直接把.class文件“塞进”元空间,而是通过一套有职责分工的流程,把原始字节流逐步转化为JVM可识别、可运行的类元数据。整个过程本质是“读取 → 解析 → 构建 → 封装”,不是简单的IO复制。
字节码怎么被读出来?
类加载器根据全限定名定位资源,再用I/O方式获取原始byte[]:
- 从本地文件系统读取(如
FileInputStream读MyClass.class) - 从JAR/WAR/ZIP等归档中解压提取(
URLClassLoader常这么做) - 通过网络下载(比如远程类加载、热更新场景)
- 甚至不依赖磁盘——由ASM、CGLIB等工具动态生成字节数组
此时得到的只是纯二进制数据,没有JVM语义,也不含任何内存地址信息。
字节流怎么变成元空间里的元数据?
JVM在加载阶段内部完成三步转化,不是“粘贴过去”:
- 校验格式:检查魔数(0xCAFEBABE)、主次版本号、常量池结构,确保符合Class文件规范
- 构建运行时结构:在元空间分配内存,填充类名、父类、接口、字段描述符、方法签名、访问标志、注解等——这才是真正的“类元数据”
-
暂存符号引用:把类、字段、方法的字符串名称(如
"java/lang/Object")先存着,留到解析阶段转成直接内存地址
注意:此时方法体字节码已载入元空间,但尚未验证或链接,还不能执行。
Class对象和元空间的关系是什么?
Class对象是Java层可见的入口,它本身存在堆中,但指向元空间里那块刚构建好的元数据:
- 每个被加载的类,在元空间中只有一份元数据模板
- Class对象就像一个“句柄”,封装了对这份元数据的访问能力
- 后续创建实例(new对象)时,JVM依据这个模板在堆中分配内存、初始化字段
也就是说,元空间存的是“类的定义”,Class对象是“通往定义的钥匙”。
元空间和老的永久代有什么关键区别?
JDK 8起用元空间替代永久代,核心变化在于内存来源和管理逻辑:
- 元空间使用本地内存(Native Memory),不受堆大小限制
- 默认无上限,靠
-XX:MaxMetaspaceSize防无限膨胀 - 类元数据(包括运行时常量池、字段/方法数据、符号表等)全部存放其中
- GC会回收不再使用的类元数据,但触发条件与堆GC不同,通常由元空间容量阈值驱动
不复杂但容易忽略:元空间不是“只存类”,它还承载了类加载器自身的信息、字符串常量、数字常量等关联数据。










