Java虚拟机的方法区是JVM规范中定义的一块内存区域,属于线程共享区域,用于存储已被虚拟机加载的类型相关信息、常量、静态变量等数据,是JVM运行时内存的重要组成部分。

方法区的核心存储内容
方法区存储的内容可以分为以下几类,不同JDK版本中部分内容的存储位置会有调整,但核心范畴基本一致:
1. 类元信息
类元信息是方法区最核心的存储内容,当虚拟机加载一个类时,会把类的结构信息提取出来存入方法区,具体包括:
- 类的全限定名、直接父类的全限定名、实现的接口列表
- 类的访问修饰符,比如public、abstract、final等
- 类的字段信息,包括字段的修饰符、类型、名称
- 类的方法信息,包括方法的修饰符、返回值类型、参数列表、字节码指令、异常表等
- 类的类加载器引用,以及对应Class对象的引用
2. 运行时常量池
运行时常量池是class文件中常量池表的运行时表示,class文件中的常量池存储了编译期生成的各种字面量和符号引用,当类加载到虚拟机后,这部分内容就会被放入运行时常量池。运行时常量池相比class文件的常量池,还具备动态性,运行时也可以将新的常量放入池中,比如String类的intern()方法就会操作运行时常量池。
3. 静态变量
静态变量也就是类变量,是被static修饰的变量,不属于任何实例对象,而是属于类本身,因此存储在方法区中。需要注意的是,在JDK 7之后,静态变量从方法区移到了堆空间中,这是方法区实现变化的一个重要调整点。
4. 即时编译器编译后的代码缓存
当虚拟机使用即时编译器(比如HotSpot的C1、C2编译器)将热点代码编译成本地机器码后,这些编译后的代码缓存也会存储在方法区中,用于提升后续执行的效率。
方法区的设计原理
方法区的设计遵循了JVM规范的逻辑定义,核心设计考量主要有以下几点:
线程共享的设计逻辑
方法区被设计为线程共享区域,是因为类元信息、运行时常量池等内容是属于类级别的,所有线程加载同一个类后,共享同一份类元信息,不需要每个线程单独存储一份,这样可以减少内存的重复占用,提升内存使用效率。
不同版本的实现差异
JVM规范只定义了方法区的逻辑功能,没有规定具体的实现方式,因此不同虚拟机、不同JDK版本有不同的实现:
- JDK 7及之前,HotSpot虚拟机使用永久代来实现方法区,永久代是堆的一个逻辑部分,受JVM内存参数限制,容易出现内存溢出问题
- JDK 8及之后,HotSpot废除了永久代,改用元空间来实现方法区,元空间不再使用堆内存,而是直接使用本地内存,默认情况下元空间的大小只受本地内存限制,大大降低了方法区内存溢出的概率
垃圾回收的设计
方法区的垃圾回收主要回收两部分内容:废弃的常量和不再使用的类。常量回收的逻辑和堆中对象的回收类似,当常量没有被任何地方引用时就可以被回收。而类的回收条件则比较严格,需要满足三个条件:该类的所有实例都已经被回收、加载该类的类加载器已经被回收、该类对应的Class对象没有被任何地方引用,且无法通过反射访问该类的方法。因此方法区的垃圾回收效率通常比较低,回收频率也较低。
代码示例验证方法区存储
我们可以通过一段简单的代码观察静态变量的存储变化,以及方法区相关的内存参数设置:
public class MethodAreaTest {
// 静态变量,JDK 7之后存储在堆中,之前存储在方法区(永久代)
private static String staticStr = "test";
public static void main(String[] args) {
// 调用intern方法,将字符串放入运行时常量池(方法区)
String str = new String("test").intern();
System.out.println(staticStr == str); // 输出true,说明指向同一个常量池中的引用
}
}
如果需要设置元空间的大小,可以使用以下JVM参数:
# 设置元空间初始大小为10MB -XX:MetaspaceSize=10m # 设置元空间最大大小为100MB -XX:MaxMetaspaceSize=100m
方法区与永久代、元空间的关系
很多开发者容易混淆方法区、永久代、元空间的概念,这里做一个简单的区分:
| 概念 | 定义 | 所属范畴 |
|---|---|---|
| 方法区 | JVM规范中定义的逻辑内存区域,规定了该区域需要存储的内容和功能 | JVM规范层面 |
| 永久代 | JDK 7及之前HotSpot虚拟机对方法区的实现方式,是堆的一部分 | 虚拟机实现层面 |
| 元空间 | JDK 8及之后HotSpot虚拟机对方法区的实现方式,使用本地内存 | 虚拟机实现层面 |
简单来说,方法区是规范,永久代和元空间是HotSpot虚拟机针对不同版本给出的具体实现,其他虚拟机比如JRockit、J9并没有永久代的概念,而是按照自己的方式实现方法区的功能。