# 方法区

  • 方法区与 Java 堆一样,是各个线程共享的内存区域
  • 方法区在 JVM 启动的时候被创建,并且它的实际的物理内存空间和 Java 堆一样可以是不连续的
  • 方法区 的大小,跟堆空间一样,可以选择固定或者可扩展
  • 方法区的大小决定了系统可以储存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误 : OOM: MetaSpace
  • 关闭 JVM 就会释放这个区域的内存
  • 元数据区大小可以使用参数 - XX: MetaSpaceSize 和 - XX: MaxMetaSpaceSize

# 方法区的内部结构

《深入理解 Java 虚拟机》书中对方法区储存内容描述如下:它用于储存已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等

# 类型信息

对于每个加载的类型 (类 class, 接口 interface, 枚举 enum, 注解 annotation), JVM 必须在方法区中储存以下类型信息

  1. 这个类型的完整有效名称 (全名 = 包名。类型)
  2. 这个类型直接父类的完整有效名 (对于 interface 或是 java.lang.Object, 都没有父类)
  3. 这个类型的修饰符 (public, abstract, final 的某个子集)
  4. 这个类型直接接口的一个有序列表

# 域 (Field) 信息

  • JVM 必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
  • 域的相关信息包括:域名称,域类型,域修饰符 (public, private, protected, static, final, volatile, transient 的某个子集)

# 方法 (Method) 信息

JVM 必须保存所有方法的以下信息,同域信息一样包括声明顺序 :

  • 方法名称
  • 方法的返回类型 (或 void)
  • 方法参数的数量和类型 (按顺序)
  • 方法的修饰符 (public, private, protected, static, final, synchronized, native, abstract 的某个子集)
  • 方法的字节码 (bytecodes), 操作数栈,局部变量表的大小 (abstract 和 native 方法除外)
  • 异常表 (abstract 和 native 方法除外)
    • 每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引

# 运行时常量池

  • 运行时常量池是方法区的一部分
  • 常量池表是 Class 文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
  • 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
  • JVM 为每个已加载的类型都维护一个常量池。池中的数据项像数组项一样是通过索引访问的
  • 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址
    • 运行时常量池,相对于 Class 文件常量池的另一个重要特征是:具备动态性
  • 运行时常量池类似于传统编程语言中的符号表,但是它所包含的数据却比符号表要更加丰富一些
  • 当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能够提供的最大值,则 JVM 会抛 OutOfMemoryError 异常

# StringTable 为什么要调整

JDK7 中将 StringTable 放到了堆空间中。因为永久代的回收效率很低,在 Full GC 的时候才会触发。而 Full GC 是老年代,永久代的空间不足的时候才会触发,这就导致 StringTable 回收效率不高。开发的时候会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存

更新于 阅读次数