方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
使用HotSpot虚拟机的用户,更愿意把方法区称为“永久代PermGen”,本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集至方法区,或者说用永久代来实现方法区而已。这样HotSpot的垃圾收集器可以像管理Java堆一样管理这部分内存,能省去专门为方法区编写内存管理代码的工作。
移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。
元空间Metaspace(JDK1.8开始使用元空间替代了永久代)本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此默认情况下元空间的大小仅受本地内存限制。
即方法区里存放着类的版本、字段、方法、接口和常量池Constant Pool Table(存储字面量和符号引用)。
符号引用包括:1、类的权限定名;2、字段名和属性;3、方法名和属性。
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对象(在动态链接中起到核心作用)。
方法信息中包含类的所有方法,每个方法包含以下信息:
指该类所有对象共享的静态变量
每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象。
为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的,类似C++虚函数表vtbl。
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(ConstantPoolTable),用于存放编译器生成的各种字面常量和符号引用,这部分内容被类加载后进入方法区的运行时常量池(RuntimeConstantPool)中存放。
在解析阶段会将这些符号引用替换为直接引用。
运行时常量池相对于Class文件常量池的另外一个特征具有动态性,Java语言并不要求常量一定只有编译期才能产生,可以在运行期间将新的常量放入池中(典型的如String类的intern()方法)。
Java虚拟机对Class文件每一部分(包括常量池)的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java虚拟机规范没有做任何细节的要求,不同的提供商实现的虚拟机可以按照自己的需要来实现这个内存区域。
个人理解:常量池只是约定的规范与数据结构,运行时常量池是如何对存放这些数据结构的具体实现。