在 Java 虚拟机(JVM)中,内存的划分主要分为三部分:堆、方法区和 Java 虚拟机栈。方法区和元空间属于 Java 虚拟机规范中的概念,两者在不同的 Java 版本中有着不同的实现方式,但它们的功能和作用是相似的。
一、方法区和元空间的概念
方法区和元空间都是用于存储类的相关信息的内存区域。 方法区属于 Java 虚拟机规范中的概念,在 JDK 8 及之前的版本中,它被实现为永久代(PermGen),用于存放静态变量、类的信息、常量、方法信息等。由于永久代的固定大小和 GC 不够灵活,导致永久代泄漏的问题十分严重,从 JDK 8 开始,Oracle 将永久代改为了元空间(Metaspace),元空间并不在虚拟机中,而是使用本地内存,因此无需担心 GC 问题。
二、方法区和元空间的特性
1、共享性
方法区和元空间中存储的信息是任何线程共享的。这一特性在多个相同的类对象使用时非常有用,因为这些对象可以共享相同的类信息,降低内存使用量。
2、只读性
方法区和元空间存储的信息是只读的,一旦类加载进入方法区或元空间,就无法修改它们。在程序运行期间,只允许读取信息,而不允许在信息上进行修改。这个特性也是使用共享内存的优势,使得类共享的信息在多线程访问时不会出现数据不一致的问题。
3、自动内存管理
方法区和元空间的内存使用由 JVM 自动进行管理,即自动进行内存分配和回收。
三、方法区和元空间的主要内容
1、类信息
类信息是指加载到方法区或元空间中的类相关的一些信息,比如类的名称、父类的名称、实现接口的名称、类字段、类方法等。 例如,在一个 Web 应用程序中,多个 Servlet 可以共享相同的 Session 对象,这些 Servlet 的类信息都放在方法区或元空间中。这使得它们可以访问相同的 Session 对象,避免了多余的内存分配。
2、常量
常量是指在程序编译期间就确定下来并赋值的值。常量池存储了常量的值,方法区或元空间存储了指向常量池的引用。 例如,String 类的对象就被存储在方法区或元空间中,而字符串常量池则存储了这些 String 对象的值。
3、运行时常量池
运行时常量池是在运行期间由 JVM 动态生成的常量池,它具有临时性和独立性。具体来说,它是在堆上分配的,因此不会像方法区或元空间一样对 GC 产生影响。 例如,在使用反射机制时,运行时常量池就会在运行时生成相应的字节码。
4、方法数据
方法数据是指类的方法相关信息,方法的指令代码不在此列。 例如,方法区或元空间中存储的是 Java 类中所有函数的字节码信息和指向对应字节码的指针,包括方法名、参数列表、返回值类型等。
四、代码示例
/**
* Java虚拟机必读—最详细的java堆、方法区、虚拟机栈介绍
* 方法区范围实例
*/
public class MethodAreaSize {
public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
CglibBean bean = new CglibBean("com.huawei.Resource:" + i, new HashMap<>());
}
}
}
class CglibBean {
public CglibBean(String key, Object value) {
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
private String key;
private Object value;
}
本文详细介绍了方法区和元空间的概念、特性、以及存储的主要内容。了解这些内存区域的信息,对于理解 JVM 内部的工作原理和性能调优都非常有帮助。