方法区存储类元信息和运行时常量池,jdk 8+由元空间(本地内存)实现,jdk 7及以前使用永久代(堆内);静态常量池在class文件中,运行时常量池在方法区,字符串字面量自jdk 7起存于堆中。

方法区存储类元信息和常量池,不是简单“放一起”,而是按规范分层组织、按版本差异落地实现。
类元信息存在哪儿:结构化存于方法区的运行时数据区
类加载后,JVM将Class文件中的类型信息(如类名、父类、接口、字段表、方法表、访问标志等)解析为内部数据结构,存入方法区。这部分是类的运行时元数据,由HotSpot用C++对象(如Klass、Method、ConstantPool等)封装,不暴露给Java代码,但可通过java.lang.Class对象间接访问。
- JDK 7及以前:这些结构放在永久代(PermGen),与堆共用GC机制,大小受
-XX:MaxPermSize限制 - JDK 8起:改由元空间(Metaspace)承载,使用本地内存,仅受系统物理内存和
-XX:MaxMetaspaceSize约束 - 每个已加载类对应一份独立元数据,卸载条件苛刻(需类加载器可被回收+无该类实例+无引用该类的其他类)
常量池分两类:静态在class里,运行时拆开放
常量池不是单一内存块,要区分两个阶段:
-
静态常量池:编译生成,嵌在class文件中,含14种常量表(如
CONSTANT_Utf8、CONSTANT_Class、CONSTANT_String等),只读,不占JVM运行内存 -
运行时常量池:类加载时,将静态常量池内容拷贝进方法区(JDK 8起即元空间),同时支持运行期动态添加(如
String.intern()、Lambda生成的引导方法)
注意:字符串字面量(如"hello")是个特例——它不进方法区。JDK 7起,字符串常量池已从方法区迁移到Java堆中;而运行时常量池里的CONSTANT_String项,只是指向堆中字符串对象的引用。
二者关系:运行时常量池依赖类元信息,但逻辑独立
类元信息描述“这个类长什么样”,运行时常量池存放“这个类用到哪些常量和符号”。两者在方法区中相邻但分离:
- 类元信息中包含一个指针,指向该类专属的运行时常量池实例
- 符号引用(如方法名+描述符)先存于运行时常量池,类加载的解析阶段才转为直接引用(如内存地址),这个过程需要查类元信息里的字段/方法表
- 运行时常量池可能跨类共享(如相同字符串字面量在堆中只存一份,多个类的常量池都引用它)
溢出风险点:别只盯着堆,元空间和字符串池都要监控
常见OOM场景暴露了存储位置的实际影响:
-
OutOfMemoryError: Metaspace→ 元空间不足,多因动态生成类(如CGLIB、JSON反序列化、热部署)未卸载 -
OutOfMemoryError: Java heap space(且堆中字符串对象暴增)→ 字符串常量池(堆里)被大量intern()塞满 -
OutOfMemoryError: PermGen space(JDK 7-)→ 永久代装不下元数据+运行时常量池
调参不能只加-Xmx。排查要结合jstat -gc看Metaspace用量、jmap -histo:live查堆内字符串分布、再配合-XX:+TraceClassLoading/Unloading确认类是否堆积。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











