在 Java 虚拟机中,方法区是可供各线程共享的运行时内存区域。
在不同的 JDK 版本中,方法区中存储的数据是不一样的。
在 JDK1.6 及之前,运行时常量池是方法区的一个部分,同时方法区里面存储了类的元数据信息、静态变量、即时编译器编译后的代码(比如 Spring 使用 IOC 或者 AOP 创建 bean 时,或者使用 cglib,反射的形式动态生成 class 信息等)等。
在 JDK1.7 及以后,JVM 已经将运行时常量池从方法区中移了出来,在 JVM 堆开辟了一块区域存放常量池。
方法区和永久代的关系
在 Java 虚拟机规范中,方法区在虚拟机启动的时候创建,虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择不在方法区实现垃圾回收与压缩。这个版本的虚拟机规范也不限定实现方法区的内存位置和编译代码的管理策略。所以不同的 JVM 厂商,针对自己的 JVM 可能有不同的方法区实现方式。
在 HotSpot 中,设计者将方法区纳入 GC 分代收集。HotSpot 虚拟机堆内存被分为新生代和老年代,对堆内存进行分代管理,所以 HotSpot 虚拟机使用者更愿意将方法区称为老年代。
方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。
我们知道在 HotSpot 虚拟机中存在三种垃圾回收现象,minor GC、major GC 和 full GC。对新生代进行垃圾回收叫做 minor GC,对老年代进行垃圾回收叫做 major GC,同时对新生代、老年代和永久代进行垃圾回收叫做 full GC。许多 major GC 是由 minor GC 触发的,所以很难将这两种垃圾回收区分开。major GC 和 full GC 通常是等价的,收集整个 GC 堆。但因为 HotSpot VM 发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的 full GC 还是 major GC。
元空间
上面说过,HotSpot 虚拟机在 1.8 之后已经取消了永久代,改为元空间,类的元信息被存储在元空间中。元空间没有使用堆内存,而是与堆不相连的本地内存区域。所以,理论上系统可以使用的内存有多大,元空间就有多大,所以不会出现永久代存在时的内存溢出问题。这项改造也是有必要的,永久代的调优是很困难的,虽然可以设置永久代的大小,但是很难确定一个合适的大小,因为其中的影响因素很多,比如类数量的多少、常量数量的多少等。永久代中的元数据的位置也会随着一次 full GC 发生移动,比较消耗虚拟机性能。同时,HotSpot 虚拟机的每种类型的垃圾回收器都需要特殊处理永久代中的元数据。将元数据从永久代剥离出来,不仅实现了对元空间的无缝管理,还可以简化 full GC 以及对以后的并发隔离类元数据等方面进行优化。
方法区的存储内容
下面的图片显示的是 JVM 加载类的时候,方法区存储的信息:
类型信息
-
类型的全限定名
-
超类的全限定名
-
直接超接口的全限定名
-
类型标志(该类是类类型还是接口类型)
-
类的访问描述符(public、private、default、abstract、final、static)
类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。常量池中每一个保存的常量都有一个索引,就像数组中的字段一样。因为常量池中保存中所有类型使用到的类型、字段、方法的字符引用,所以它也是动态连接的主要对象(在动态链接中起到核心作用)。
字段信息(该类声明的所有字段)
-
字段修饰符(public、protect、private、default)
-
字段的类型
-
字段名称
方法信息
方法信息中包含类的所有方法,每个方法包含以下信息:
-
方法修饰符
-
方法返回类型
-
方法名
-
方法参数个数、类型、顺序等
-
方法字节码
-
操作数栈和该方法在栈帧中的局部变量区大小
-
异常表
类变量(静态变量)
指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。
指向类加载器的引用
每一个被 JVM 加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
指向 Class 实例的引用
类加载的过程中,虚拟机会创建该类型的 Class 实例,方法区中必须保存对该对象的引用。通过 Class.forName(String className) 来查找获得该实例的引用,然后创建该类的对象。
方法表
为了提高访问效率,JVM 可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用,包括父类中继承过来的方法。这个表在抽象类或者接口中是没有的,类似 C++ 虚函数表 vtbl。
运行时常量池
Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面常量和符号引用,这部分内容被类加载后进入方法区的运行时常量池中存放。
运行时常量池相对于 Class 文件常量池的另外一个特征具有动态性,可以在运行期间将新的常量放入池中(典型的如 String 类的 intern() 方法)。
参考
https://www.cnblogs.com/chengpeng15/p/9850690.html
https://www.breakyizhan.com/javamianshiti/2839.html