虽然《JVM 虚拟机规范》把方法区描述为堆的一个逻辑部分,但是他还有一个别名叫做 Non-Heap(非堆),目的就是与堆分开来。所以,方法区可以看作独立于 JVM 堆的内存空间。
方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器后的代码缓存等。
对每个加载的类型(class、interface、enum、注解annotation),JVM 必须在方法区中存储如下类型信息:
全类名(包名+类名)父类的全类名(出了 interface 和 Object)类型的修饰符这个类型直接接口的一个有序列表 JVM 必须在方法区中保存类型的所有域的以下相关信息以及域的声明顺序
域名称域类型域修饰符 JVM 必须保存所有方法的以下相关信息以及方法的声明顺序
方法名称方法的返回类型方法参数的数量类型(按顺序)方法的修饰符方法的字节码文件、操作数栈、局部变量表及大小异常表 一个 Java 源文件中的类、接口,编译后产生一个字节码文件,而 Java 中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,所以就把它们存放到了常量池里,在动态链接的时候会用到运行时常量池。
常量池中保存了数量值、字符串值、类引用、字段引用、方法引用。
常量池是 Class 文件的一部分,用于存放编译器生成的各种字面量和符号引用,这部分内容在类加载后存放到方法区的运行时常量池中。
运行时常量池与常量池的一大区别就是动态性,运行期间可以把新的常量放入池中。
首先明确:只有 Hotspot 才有永久代。
时间线特点JDK6及之前有永久代,静态变量存放在永久代上JDK7有永久代,但已经开始逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中JDK8及之后无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆 官方解释:Hotspot 与 JRocket 做了合并,因为 JRocket 使用了元空间,为了让 JRocket 的用户不需要配置永久代
更容易的理解:
1. 因为永久代要保存类信息,而加载多少类很难确定,所以永久代的大小就很难确定,如果太小,就很容易报 OOM,如果太大,则浪费空间,所以将永久代,所以使用仅受本地内存大小限制的元空间是更好的选择;
2. 对永久代的调优很困难。
我们在开发中会大量创建字符串,如果存在永久代,永久代就会很容易满,所以会很容易触发 full gc,这个过程十分慢,而如果放在堆空间中,则能够及时回收。
方法区的回收效果比较难令人满意,尤其是类型的卸载,条件十分苛刻。
方法区的垃圾回收主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
常量包括: 文本字符串或声明为 final 的常量值等;符号引用:类或接口的全限定名、字段的名称和描述符、方法的名称和描述符 判断一个类型是否属于“不再被引用的类”的条件十分苛刻,需要同时满足下面三个条件: 该类及其派生子类的所有实例都已经被回收;加载该类的类加载器已经被回收;该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。