方法区是JVM中存储类元数据的共享区域,存放类名、父类、接口、字段、方法签名、静态变量、常量池及JIT编译代码等,JDK 8起由元空间实现并位于本地内存。

方法区是 JVM 中存放类元数据的共享内存区域,不是用来存对象实例的,而是存“类本身的信息”——比如类名、父类、接口、字段、方法签名、静态变量、常量池、JIT 编译后的代码等。它和堆一样被所有线程共享,但逻辑上独立于堆,HotSpot 中从 JDK 8 起用 元空间(Metaspace) 实现,且物理上位于本地内存(堆外),不再受 -XX:MaxPermSize 限制。
方法区里到底存什么
可以理解为:一个类被加载进 JVM 后,“这个类长什么样”的全部描述都放在这里。
- 类型信息:类的全限定名、父类名、实现的接口列表、访问修饰符(public / final / abstract 等)
- 常量池:编译期生成的字面量(如字符串 "hello")、符号引用(如方法名、字段名)
- 字段信息:每个字段的名称、类型、修饰符、属性(如是否 static)
- 方法信息:方法名、返回类型、参数列表、字节码、异常表、注解等
- 静态变量:用 static 声明的变量(注意:变量值本身可能在堆中,比如 static Object obj = new Object(),obj 引用存在方法区,new 出的对象在堆)
- JIT 编译缓存:热点方法被编译成的本地机器码,也缓存在方法区(元空间)中
方法区和堆、栈怎么配合工作
写一句 Sample s = new Sample("test");,背后三者分工明确:
-
方法区:存
Sample.class的完整结构(类定义、构造方法字节码、静态字段等) -
Java 虚拟机栈:存局部变量
s(即指向堆中对象的引用) -
堆:存真正创建的
Sample实例对象,对象头里还包含一个指针,指向方法区中的Sample类型信息
这种设计让多个对象能共用同一份类定义,节省内存,也支撑了反射、动态代理等机制。
立即学习“Java免费学习笔记(深入)”;
怎么查看方法区实际内容
不能像打印堆对象那样直接输出,但可通过工具间接观察:
-
jstat:监控元空间使用情况
jstat -gc <pid>查看 MetaspaceCapacity、MetaspaceUsed、MaxMetaspaceSize -
jmap:导出类元数据统计
jmap -clstats <pid>显示已加载类数量、平均大小等jmap -histo:live <pid>虽主要看堆,但配合 -F 可辅助判断类加载是否异常增长 - JVisualVM / JConsole:图形化查看“Metaspace”使用曲线,设置阈值告警
- -XX:+PrintGCDetails + -XX:+PrintGCTimeStamps:GC 日志中会记录元空间回收行为(如 Full GC 是否触发元空间清理)
-
javap:反编译 class 文件,查看编译后的方法区“蓝图”
javap -v Sample.class可看到常量池、字段表、方法表、属性等原始结构
常见误区与注意事项
容易混淆的几个点要特别注意:
- 方法区 ≠ 永久代:JDK 8+ 已移除永久代(PermGen),改用元空间;永久代是历史概念,仅存在于 JDK 7 及以前
- 字符串常量池位置变化:JDK 7 起,字符串常量池从方法区移到堆中;但类常量池(class 文件里的 constant_pool)仍在方法区
- 静态变量 vs 静态变量的值:static int x = 10 → x 的定义和初始值信息在方法区;static List<String> list = new ArrayList<>() → list 引用存方法区,new 出的 ArrayList 对象在堆
- 元空间默认无上限(只受本地内存限制),但生产环境务必设置
-XX:MaxMetaspaceSize,否则可能因加载过多类导致本地内存耗尽


















