class对象存放在堆中,元空间仅存储类的元数据(如常量池、字段信息、方法字节码等)及静态变量的符号引用,静态变量实际值与class实例一同位于堆中。

Class 对象变量并不存放在元空间中——这是个关键前提,必须先厘清。
Class 对象本身在堆中,不是元空间
从 JDK 7 开始,HotSpot 虚拟机将 Class 对象实例(即运行时通过类加载器创建的 java.lang.Class 实例)统一存放于Java 堆,而非方法区或元空间。这一点在《深入理解 Java 虚拟机》及 HotSpot 官方实现中明确:Class 对象是 GC 可管理的普通对象,和 String、ArrayList 一样分配在堆里。
而元空间(Metaspace)只存储类的元数据(metadata),比如:
- 常量池(运行时常量池的一部分)
- 字段信息(名称、描述符、修饰符)
- 方法字节码、注解、接口实现列表
- 静态变量的符号引用(但不是静态变量的值本身)
注意:静态变量(static 字段)的实际值,在 JDK 8+ 也随 Class 对象一起存于堆中;元空间里只有其符号描述,不存值。
实战验证 Class 对象位置的方法
用 JOL(Java Object Layout)工具可直接观察:
- 启动参数确保开启指针压缩:
-XX:+UseCompressedOops -XX:+UseCompressedClassPointers - 写一段代码获取 Class 对象并打印布局:
System.out.println(ClassLayout.parseInstance(String.class).toPrintable()); - 输出中会显示该 Class 实例的地址、大小(通常约 48–64 字节)、对象头(含 Mark Word + Klass Pointer)、实例数据(如 name、classLoader 等字段)——全部结构都符合堆中普通对象布局,且 Klass Pointer 指向元空间中的类型元数据
元空间中真正存储什么?怎么看?
要观察元空间内容,需结合 JVM 参数与诊断工具:
- 添加
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps并触发 Full GC,观察 Metaspace 使用量变化 - 用
jstat -gc <pid></pid>查看MU(Metaspace used)、MC(Metaspace capacity) - 用
jcmd <pid> VM.native_memory summary scale=MB</pid>查看本地内存中 Metaspace 分配情况 - 用
jhsdb jmap --heap --binaryheap或 JFR(Java Flight Recorder)抓取元数据快照,分析类定义数量、符号表大小等
你会发现:加载越多类(尤其动态生成类,如 CGLIB、ASM),元空间用量持续上升,但每个 Class 对象自身仍稳定存在于堆中,且会被 GC 回收(当类加载器被卸载时)。
一个易错点:static 变量 ≠ 存在元空间
很多人误以为 static int x = 1 的值存在方法区或元空间。实际上:
- 编译期,
x的初始值 1 存在 class 文件的ConstantValue属性中(加载时进入元空间) - 运行期,JVM 在堆中为该 Class 对象分配字段存储空间,
x的当前值就放在这里(例如String.class对象内部有个static final String value字段,其引用指向堆中另一个 String 对象) - 所以修改 static 变量,改的是堆中 Class 实例的字段值,不是元空间里的常量










