08-方法区
方法区的演变
只有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 space
、java.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 | public class MethodAreaTest { |
全局常量:static final
每个全局常量在编译的时候就会被分配
常量池与运行时常量池
方法区内部包含了运行时常量池
字节码文件中包含了常量池
当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
常量池的理解
常量池,可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型、字面量等信息(主要是字面量和符号引用)
一个 java 程序编译生成字节码文件后,字节码文件需要大量数据支持进行解析,如果将数据直接存进字节码,文件过大所以将数据存进常量池,字节码中包含了指向常量池的引用
运行时常量池
字节码文件中的常量池表经过类加载器放到方法区后,对应的结构就称为运行时常量池
当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
注意:
1、运行时常量池是方法区的一部分。
2、运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用 此时不再是常量池中的符号地址了,这里换为真实地址。
3、运行时常量池相较于Class文件中的常量池的特征:动态性
4、符号地址变为真实地址其实就是,在*.class文件
被加载到内存以后,将*.class文件
中常量池中的#x
符号地址,转化为内存中的地址。
方法区的垃圾收集
前言:
方法区内常量池之中主要存放的两大类常量:字面量和符号引用。
字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。
符号引用则属于编译原理方面的概念,包括
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
方法区中主要回收
1、常量池中废弃的常量
2、不再使用的类型
只要常量池中的常量没有被任何地方引用,就可以被回收(与堆中回收对象类似)
判断一个类是否要进行回收(*)