方法区的演变


只有HotSpot虚拟机才有永久代

1、jdk1.6之前:又永久代,静态变量存储在永久代上

2、jdk1.7:又永久代,字符串常量池,静态变量移除,保存在堆中

3、jdk1.8以后:无永久代,类型信息、字段、方法、常量池保存在本地内存的元空间,但字符串常量池,静态变量仍在堆


永久代为什么要被元空间替换?

1、为永久代设置空间大小是很难确定的如果动态加载类过多,容易产生永久代的OOM,而元空间使用本地内存,只受本地内存的影响

2、对永久代调优困难


字符串常量池为什么要放到堆空间?

因为永久代的回收率很低,在进行full GC的时候才会触发,而full GC是老年代空间不足、永久代不足时才会触发,所以导致回收效率不高。但是实际中又有大量字符会被创建,回收效率低导致永久代空间不足。放到堆里,可以及时回收。


方法区的概述

1、方法区在JVM启动的时候被创建,它的实际的物理内存空间可以是不连续的(关闭 JVM 就会释放这个区域的内存)

2、方法区时逻辑上是堆的一个组成部分,但是在不同虚拟机里头实现是不一样的,最典型的就是永久代(PermGen space)和元空间(Metaspace)
(注意:方法区是一种规范,而永久代和元空间是它的一种实现方式)

3、方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:(java.lang.OutOfMemoryError:PermGen spacejava.lang.OutOfMemoryError:Metaspace)

  • 加载过多的jar包
  • tomcat部署的过程过多
  • 反射类太多

4、jdk7及以前,将方法区的实现称为永久代,jdk8之后,使用元空间取代了永久代

5、元空间和永久代的区别:元空间不在虚拟机设置的内存中,而是使用本地内存


设置方法区大小与OOM

jdk7及以前

-XX:PermSize=100m(默认值是20.75M)

-XX:MaxPermSize=100m(32位机器默认是64M,64位是82M)

jdk1.8及以后

-XX:MetaspaceSize=100m(windows默认约等于21M)

-XX:MaxMetaspaceSize=100m(默认是-1,即没有限制)


方法区的内部结构

存储已被虚拟机记载的类型信息、常量、静态变量、即时编译器编译后的代码缓存、域信息、方法信息

类型信息

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

1、类型的完整有效名称(全名=包名.类名)

2、直接父类的完整有效名(接口和Object类没有)

3、修饰符

4、直接接口的一个有序列表


域信息

域名称,域修饰符(public,private……),域类型


方法信息

1、方法名称

2、方法返回类型

3、方法参数的数量和类型(按顺序)

4、方法的字节码,操作数栈和局部变量表的大小


non-final的类变量

1、静态变量和类关联在一起,随着类的加载而加载

2、类变量被所有类的实例共享,即使没有类实例也可以访问它

以下代码不会报空指针异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MethodAreaTest {
public static void main(String[] args) {
Order order = null;
order.hello();
System.out.println(order.count);
}
}
class Order {
public static int count = 1;
public static final int number = 2;

public static void hello() {
System.out.println("hello!");
}
}

全局常量:static final

每个全局常量在编译的时候就会被分配


常量池与运行时常量池

方法区内部包含了运行时常量池

字节码文件中包含了常量池

当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址


常量池的理解

常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型、字面量等信息(主要是字面量和符号引用)

一个 java 程序编译生成字节码文件后,字节码文件需要大量数据支持进行解析,如果将数据直接存进字节码,文件过大所以将数据存进常量池,字节码中包含了指向常量池的引用


运行时常量池

字节码文件中的常量池表经过类加载器放到方法区后,对应的结构就称为运行时常量池

当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

注意:

1、运行时常量池是方法区的一部分。

2、运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用 此时不再是常量池中的符号地址了,这里换为真实地址。

3、运行时常量池相较于Class文件中的常量池的特征:动态性

4、符号地址变为真实地址其实就是,在*.class文件被加载到内存以后,将*.class文件中常量池中的#x符号地址,转化为内存中的地址。


方法区的垃圾收集

前言:

方法区内常量池之中主要存放的两大类常量:字面量和符号引用。

字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。

符号引用则属于编译原理方面的概念,包括

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

方法区中主要回收

1、常量池中废弃的常量

2、不再使用的类型

只要常量池中的常量没有被任何地方引用,就可以被回收(与堆中回收对象类似)

判断一个类是否要进行回收(*)