JVM 中方法区的大致区域浏览图:
《深入理解 Java 虚拟机》书中对方法区( Method Area) 存储内容描述如下:它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等。
-
类型信息
对每个加载的类型(类 class、接口 interface、枚举enum、注解annotation),JVM 方法区中存储以下类型信息:
-
这个类型的完整有效名称(全名=包名.类名)
-
这个类型直接父类的完整有效名(对于 interface 或是 java.lang.Object,都没有父类)
-
这个类型的修饰符(public,abstract ,final 的某个子集)
-
这个类型直接接口的一个有序列表
-
域(Field)信息
-
JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
-
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient 的某个子集)
-
方法(Method) 信息JVM 必须保存所有方法的以下信息,同域信息一样包括声明顺序:
-
方法名称
-
方法的返回类型
-
方法参数的数量和类型(按顺序)
-
方法的修饰符(public ,private, protected , static ,final, synchronized, native,abstract 的一个子集)
-
方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native方法除外)
-
异常表 (abstract 和 native 方法除外)
-
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
-
non-final 的类变量
-
静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分。
-
类变量被类的所有实例共享,即时没有类实例时你也可以访问它。
-
-
全局常量:static final
被声明为 final 的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。
-
运行时常量池 vs 常量池
-
方法区中,内部包含了运行时常量池
-
字节码文件,内部包含了常量池
-
要弄清楚方法区,需要理解 ClassFile ,因为加载类的信息都在方法区。
-
要弄清楚方法区的运行时常量池,需要理解清楚 ClassFile 中的常量池。
-
-
为什么需要常量池?
一个java源文件中类、接口、编译后产生一个字节码文件。而Java 中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池,比如:如下的代码:
public class SimpleClass{ public void sayHello(){ System.out.println("hello"); } }
虽然只有 194 字节,但是里面却使用了 String、 System、PrintStream及 Object 等结构。这里代码量其实已经很小了。如果代码多,应用到的结构会更多!这里就需要常量池了!
小结:常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
-
运行时常量池
-
运行时常量池(Runtime Constant Pool) 是方法区的一部分。
-
常量池表(Constant Pool Table) 是 Class 文件的一部分,用于存放编译期生成的各种字面量与符号应用,这部分内容将在类加载后存放到方法区的运行时常量池中。
-
在加载类和接口到虚拟机后,就会创建对应的运行时常量池。
-
JVM 为每个已加载到类型(类或接口)都维护了一个常量池。池中的数据项像数组项一样,是通过索引访问的。
-
运行时常量池中包含多种不同的常量,包括编译器就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。
-
运行时常量池,相对于 Class 文件常量池的另一重要特征是:具备动态性。
-
- 运行时常量池类似于传统编程语言中的符号表(symbol table),但是它所包含的数据却比符号表要更加丰富一些。
-
当创建类或者接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所提供的最大值,则 JVM 会抛 OutOfMemoryError 异常
-