图1
2 方法区的具体理解
1)方法区可以看作是一块独立于java堆的内存空间。
2)方法区于java堆一样,是各个线程共享的内存区域。
3)方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展长度。
5)关闭JVM会释放这个区域的内存。
3 Hotspot中方法区的演进
jdk7以前,习惯上把方法区称为永久代,jdk8以后,使用元空间取代了永久代。本质上方法区并不等价于永久代,JRockit和J9都没有方法区的概念。
相比之下,使用永久代因为还是使用 的虚拟机本身的内存,所以更加容易导致oom。
jdk8以后,元空间取代了方法区,因为JRockit被收购,二者整合时方法区改为使用本地内存。最大的区别就是元空间不在虚拟机设置的内存中,而是使用的本地内存。
4 方法区的内部结构
方法区用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。(经典但不绝对)
类型信息:
对每个加载的类型(类class、接口interfence、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息。
1)这个类型的完整有效名称(全名=包名.类名)
2)这个类型直接父类的完整有效名。
3)这个类型的修饰符(public,abstract,final的某个子集)
4)这个类型直接接口的一个有序列表。
域(Filed)信息:
1)JVM必须在方法区中保存类型的所有域对象的相关信息以及域声明顺序。
2)域相关的信息包括:域名称、域类型、域修饰符。
方法信息:
1)方法名称
2)方法的返回类型
3)方法参数的数量和类型(按顺序)
4)方法的修饰符
5)方法的字节码、操作数栈、局部变量表的大小(abstract和native除外)
6)异常表(abstract和native除外)
non-final的类变量:
1)静态变量和类关联在一起,随着类的加载而加载,它们成为数据在逻辑上的一部分。
2)类变量被类的所有实例共享,即使没有类实例时你也可以访问它。
补充说明:被声明为final的类的处理方法不同,在编译的时候就会被分配空间以及初始化值。如图二所示。

图2
运行时常量池和常量池
常量池:
一个java文件中的类、接口,编译后产生一个字节码文件。而java中的字节码需要 数据支持,通常这些数据支持会很大以至于不能直接存在字节码里,换另外一种方式,存在常量池,这个字节码包含了指向常量池的引用。里面包括的类型有数量值、字符串值、类引用、字段引用、方法引用。
小结:可以看作是一张表,虚拟机指令根据这张表找到要执行了类名、方法名、参数类型、字面量等类型。
运行时常量池:
1)运行时常量池时方法区的一部分,而常量池表时class文件第一部分。
2)运行时常量池加载到虚拟机机后,就会创建相应的运行时常量池。
3)JVM为每个已加载的类型都维护一个常量池。池中的数据项就像数组项一样,是通过索引访问的。
4)运行时常量池相较于class文件常量池,其具有动态性
5)如果创建类或者接口的运行时常量池时,如果所需的空间超过了方法区所能提供的最大值,就会oom。
5 方法区的演进细节
1)首先明确一点:只有HotSpot才有永久代
2)Hotspot中方法区的变化:
jdk1.6之前:有永久代,静态变量存放在永久代上。
jdk1.7:有永久代,但是已经开始去永久代化,静态变量和字符串常量池移到堆中。
jdk1.8:无永久代,类型信息、字段、方法
常量保存在本地内存的元空间,但字符串常量池和静态变量依旧存在堆上。
永久代为什么要被元空间替换:
1)为永久代设置空间大小是很难确定的。
2)对永久代进行调优是很困难的。
String Table为什么要移动位置:
因为永久代的回收效率很低,在full gc的时候才会被触发,导致stirng table的回收效率很低,但是我们开发中会创建很多的Sting类型的对象,所以将其移动到堆区,能及时回收内存。
6 方法区的垃圾回收
1)方法区的约束时很宽松的,可以实现垃圾回收也可以不实现。
2)方法区的垃圾回收主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
