概述
/**
* 【运行时数据区-方法区-概述】
* https://docs.oracle.com/javase/specs/jvms/se8/html/index.html
*
* <what>
* The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads.
* 多个线程共享;
* The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process.
* 方法区 类似于 常规语言 "编译代码的存储区",或类似于操作系统进程中的“文本”段;
* It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors,
* including the special methods (§2.9) used in class and instance initialization and interface initialization.
* 它存储每个类的结构:
* 运行时常量池、字段和方法数据、方法和构造函数的代码、类和实例初始化以及接口初始化中使用的特殊方法;
* The method area is created on virtual machine start-up.
* 方法区在虚拟机启动时创建;
* Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it.
* 尽管方法区在逻辑上是堆的一部分,但简单的实现可能会选择 不进行垃圾收集或压缩;
* This specification does not mandate the location of the method area or the policies used to manage compiled code.
* Java8的规范 不要求指定方法区域的位置 或 具体存储的内容;
* The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary.
* 方法区的大小 可以是固定大小,也可以根据计算要求进行扩展,如果不需要更大的方法,可以缩小;
* The memory for the method area does not need to be contiguous.
* 方法区域的内存 在物理上不需要是连续的;
*
* 方法区大小 决定 系统可以存储多少个类,如果定义类太多,将会引发 java.lang.OutOfMemoryError PermGen space 或 java.lang.OutOfMemoryError Metaspace;
*
* <HotSpot方法区的演进>
* JDK7及之前,方法区的实现 使用 永久代(使用JVM内存);
* JDK8及之后,方法区的实现 使用 元空间(使用本地内存);
*
* <设置方法区内存大小>
* JDK7及以前
* -XX:PermSize=
* 永久代 初始大小,默认20.75MB
* -XX:MaxPermSize=
* 永久代 最大内存,默认 32位机器 64MB、64位机器 82MB
*
* JDK8及以后
* -XX:MetaspaceSize=
* 元空间 初始大小
*
* 对于 64位的服务器 JVM 默认 MetaspaceSize 值为 21MB,称为 高水位线;-MaxMetaspaceSize 是-1 ,无限制;
* 一旦 Metaspace 触及 21MB,将会进行 FullGC,卸载没有用的类,高水位线 将被重置;
*
* -XX:MaxMetaspaceSize=
* 元空间 最大内存
*
*/
内部结构
/**
* 【方法区---内部结构】
* 存储 类元数据信息(类型信息、域信息、方法信息、常量、static变量、JIT编译后的代码缓存等);
*
* <类型信息>
* 每个加载的类型(class、interface、Enum、Annotation),JVM必须存储以下信息:
* 当前类型的 完全限定名;
* 当前类型的 修饰符;
* 当前类型的 直接父类 完全限定名;
* 当前类型的 直接接口的有序列表;
*
* <域信息(Field)>
* JVM必须 在方法区 保存当前类型的 所有Field相关信息 及 Field声明顺序:
* Field类型、Field名称、Field修饰符
*
* ***final修饰的属性,编译期就已经赋初始值
* eg:
*
* 源码:
* public final static String s = "a";
*
* public final String b = "b";
*
* 字节码反编译:
* public static final java.lang.String s;
* descriptor: Ljava/lang/String;
* flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
* ConstantValue: String a
*
* public final java.lang.String b;
* descriptor: Ljava/lang/String;
* flags: ACC_PUBLIC, ACC_FINAL
* ConstantValue: String b
*
*
* <Method信息>
* JVM必须 在方法区 保存当前类型的 所有Method相关信息 及 Method声明顺序:
* 方法名称、
* 方法返回值类型、
* 方法参数的数量及类型(按顺序)
* 方法修饰符、
* 方法的字节码、操作数栈、局部变量表
* 异常表
*
* <常量池>
* a,常量池:
* 字节码文件 中的一部分,包括:对 数量值、字符串值、类型引用、Field引用、方法引用 的符号引用;
*
* eg:
* Constant pool:
* #1 = Methodref #5.#20 // java/lang/Object."<init>":()V
* #2 = String #10 // b
* #3 = Fieldref #4.#21 // com/an/methodarea/Test.b:Ljava/lang/String;
* #4 = Class #22 // com/an/methodarea/Test
* #5 = Class #23 // java/lang/Object
* ...
*
* ***为什么需要常量池?
* 一个字节码文件 需要 很多内容的支持,如果 每个字节码文件都要 把所有的内容 都写到 一个文件中,将会很大;
* 可以 使用 常量池的符号引用 ,每个 字节码文件 对于 相同的内容 都可以 通过 引用得到;
*
* b,运行时常量池:
* 方法区 的一部分;
* JVM 为每个已加载的类或接口 都维护一个运行时常量池;
* 运行时常量池中的数据项 和数组项 类似,通过索引访问;
* 运行时常量池中包含多种不同的常量,包括 编译期就已经确定的数值字面量、到运行期解析后才能获得的方法或字段的引用(此时不再是常量池中的符号地址,转为真实地址);
* 当创建类或接口的运行时常量池时,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,抛出OutOfMemoryError;
*/
![]()
![]()
![]()
演进
/**
* 【方法区---演进-why-内存位置】
*
* <演进>
* ***只有 HotSpot VM才有 永久代,JRocket、J9 不存在;
*
* JDK 1.6 及之前
* 有 永久代(JVM内存)
* 类元数据信息 存放在 永久代;
*
* JDK1.7
* 有 永久代(JVM内存)
* 类信息、字段信息、方法信息、运行时常量池、JIT代码缓存 保存在 永久代(JVM内存);
* ***字符串常量池 与 static变量 移到 堆中;
*
* JDK8及以后
* 无 永久代
* 类信息、字段信息、方法信息、运行时常量池、JIT代码缓存 保存在 元空间(本地内存);
* ***字符串常量池、static变量 保存在 堆中;
*
*
* <永久代 为什么 被 元空间 替换?>
* 为 永久代 设置内存大小很难确定
* 有些场景下,加载太多class,容易 导致 FullGC,如果 FullGC 后,内存不足,容易导致 OOM;
* 元空间 使用 本地 内存,受 本地内存 限制;
*
* <字符串常量池 为什么 被移到 堆中?>
* jdk 7 将 字符串常量池 移至堆中,因为 永久代的回收效率很低,只有FullGC 的时候 才会 回收 永久代;
* 在 开发中 会创建大量的字符串,回收效率太低,导致 永久代 空间不足;
*
* <static变量的内存位置>
* static变量 引用的对象 始终都在 堆中;
* static变量 本身 在JDK7及以后 分配在 堆中;
*
* <方法区的GC>
* JVM规范 对方法区的约束是非常宽松的,可以不要求JVM在方法区中实现GC;
*
* 方法区的GC内容:
* a,常量池中废弃的常量
* 常量:
* 字面量
* 文本字符串、声明为final的常量值;
* 符号引用
* eg: 类或接口的全限定名、字段的名称和描述符、方法的名称和描述符;
*
* b,不再使用的类型
*
*/