方法区是jvm所有线程共享的内存区域,用于存储已加载类的元数据(字段、方法、运行时常量池、继承关系等),class对象则存于堆中,作为访问这些元数据的入口和接口。

方法区是JVM中所有线程共享的内存区域,核心作用是存储已被加载类的元数据信息,而不是Class对象本身——Class对象实际存放在堆中,它只是访问方法区元数据的“入口”和“接口”。
方法区存什么:真正的元数据内容
方法区存储的是类在运行时所需的结构化描述信息,即“类的元数据”,包括:
- 类的字段(Field)定义:名称、类型、修饰符(public/private等)、偏移量
- 方法(Method)信息:方法名、参数类型、返回类型、字节码指令、异常表、访问标志
- 运行时常量池(Runtime Constant Pool):编译期生成的字面量(如字符串字面量、数字常量)和符号引用(如类名、字段名、方法名的符号表示)
- 类的继承与实现关系:父类索引、接口表、类访问标志(如ACC_FINAL、ACC_INTERFACE)
- 静态变量(static fields)的初始引用位置:注意,从JDK 8起,static变量的值本身存储在堆中(属于对应Class对象的字段),但其“定义信息”和“初始值占位”仍由方法区元数据描述
Class对象在哪?它不是元数据
java.lang.Class 是一个普通Java对象,由JVM在类加载的“加载阶段”自动创建,并始终分配在堆内存中。它的关键角色是:
- 封装方法区中该类元数据的引用(内部持有指向元数据结构的指针)
- 提供反射能力的统一入口:通过 getClass().getDeclaredMethods() 等调用,最终查的是方法区里的方法元数据
- 作为类级别锁(synchronized(Class.class))的锁定目标——本质是锁定该Class对象实例,间接控制对方法区元数据的并发访问
特别提醒:Class对象 ≠ 元数据。混淆二者是常见误区。就像“身份证”不是“本人”,Class对象是元数据的“代理”或“句柄”。
类加载过程中方法区与堆的协作流程
当JVM首次主动使用某个类(如 new、static字段访问、反射等),触发类加载,三步紧密协同:
- 获取字节码:根据全限定名定位.class文件(可来自磁盘、jar、网络或数据库)
- 构建元数据:将字节码解析为方法区内的运行时数据结构(如常量池表、字段表、方法表)
- 创建Class对象:在堆中new出一个java.lang.Class实例,初始化其内部字段(如name、classLoader、reflectionData等),使其指向刚建好的方法区元数据
此后所有对该类的反射操作、静态成员访问、实例化,都依赖这个堆中的Class对象作为跳板,去读取方法区的真实元数据。
JDK 8+ 的关键变化:元空间取代永久代
自JDK 8起,方法区的具体实现从“永久代(PermGen)”改为“元空间(Metaspace)”,主要差异在于:
- 元空间使用本地内存(Native Memory),不再受JVM堆大小限制,避免了永久代OOM问题
- 类元数据的生命周期与ClassLoader强绑定:当ClassLoader被回收,其加载的类元数据(及关联的Class对象)才可能被卸载
- 静态变量(static fields)的值明确存于堆中,作为对应Class对象的实例字段存在(例如String.class有一个private static final String name = "java.lang.String")











