方法区(Method Area) 与Java堆一样, 是各个线程共享的内存区域, 它用于存储已被虚拟机加载的类型信息、 常量、 静态变量、 即时编译器编译后的代码缓存等数据。 虽然《Java虚拟机规范》 中把方法区描述为堆的一个逻辑部分, 但是它却有一个别名叫作“非堆”(Non-Heap) , 目的是与Java堆区分开来。
《Java虚拟机规范》 对方法区的约束是非常宽松的, 除了和Java堆一样不需要连续的内存和可以选择固定大小或者可扩展外, 甚至还可以选择不实现垃圾收集。 相对而言, 垃圾收集行为在这个区域的确是比较少出现的, 但并非数据进入了方法区就如永久代的名字一样“永久”存在了。 这区域的内存回收目标主要是针对常量池的回收和对类型的卸载, 一般来说这个区域的回收效果比较难令人满意, 尤其是类型的卸载, 条件相当苛刻, 但是这部分区域的回收有时又确实是必要的。
以前Sun公司的Bug列表中, 曾出现过的若干个严重的Bug就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。根据《Java虚拟机规范》 的规定, 如果方法区无法满足新的内存分配需求时, 将抛出OutOfMemoryError异常。
运行时常量池
运行时常量池(Runtime Constant Pool) 是方法区的一部分。
Class文件中除了有类的版本、 字段、 方法、 接口等描述信息外, 还有一项信息是常量池表(Constant Pool Table) , 用于存放编译期生成的各种字面量与符号引用, 这部分内容将在类加载后存放到方法区的运行时常量池中。Java虚拟机对于Class文件每一部分(自然也包括常量池) 的格式都有严格规定, 如每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、 加载和执行, 但对于运行时常量池,《Java虚拟机规范》 并没有做任何细节的要求, 不同提供商实现的虚拟机可以按照自己的需要来实现这个内存区域, 不过一般来说, 除了保存Class文件中描述的符号引用外, 还会把由符号引用翻译出来的直接引用也存储在运行时常量池中[1]。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性, Java语言并不要求常量一定只有编译期才能产生, 也就是说, 并非预置入Class文件中常量池的内容才能进入方法区运行时常量池, 运行期间也可以将新的常量放入池中, 这种特性被开发人员利用得比较多的便是String类的intern()方法。既然运行时常量池是方法区的一部分, 自然受到方法区内存的限制, 当常量池无法再申请到内存时会抛出OutOfMemoryError异常。[1] 关于Class文件格式、 符号引用等概念可参见第6章。
总结
方法区只是逻辑上存在,可以理解方法区是一个规范、是一个接口。而永久代或元空间则是它的具体实现;关于永久代和元空间可以看看这篇文章 https://www.jianshu.com/p/22b6501a041c
它存储了每一个
类的结构性信息
;就是类的模板,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和 普通方法的字节码内容方法区中的垃圾回收存在很少很少,大部分都在堆中
方法区在JVM中也是一个非常重要的区域,它与堆一样,是被
线程共享
的。 在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。