文章目录
1 运行时数据区结构图
- 从线程方面来看
- 堆栈,方法区之间的交互图
2 方法区的基本理解
- 方法区与java堆一样,是各个线程共享的内存区域
- 方法区在jvm启动是被创建,实际物理内存和java堆区一样都是不连续的
- 方法区大小决定了系统可以保存多少类,如果太多会抛出内存溢出错误:
java.lang.outofMemoeyError:permgen space
或者java.lang.outofMemoeyError:Metaspace
3 hotspot中方法区的演进
-
在jdk7 以前将方法区(相当于接口)称为永久代,jdk8 开始使用元空间取代了永久代,
-
现在看来,使用永久代更容易导致oom
-
元空间的本质和永久代类似,都是对jvm中方法区的实现,不过其区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存
-
永久代,元空间二者并不只是名字变了,内部结构也调整了
4 设置方法区内存的大小
-
jdk7及以前
通过 -XX:PermSize 来设置永久代分配空间。默认值是20.75m -
-XX:MaxPermSize 来设定永久代最大可分配空间。32位机器默认是64m,64位机器模式是82m。
-
jdk8及以后
-XX:MetaspaceSize=100m
设置永久代分配空间
-XX:MaxMetaspaceSize=100m
设置永久代最大分配空间
5 如何解决oom
-
要解决oom异常或 head space 的异常,首先通过内存映像分析工具进行分析(如 jvisualvm,jprofiler)对dump出来的堆存储块照进行分析 先分清楚到底是出现了 内存泄露(指的是有引用指向无效的对象导致无法gc) 还是内存溢出
-
如果是内存泄露,通过工具查看泄露对象到gc roots的引用连。
-
如果不存在内存泄露,也就是内存中的对象确实都还必须活着,那就检查虚拟机参数(-Xmx与-Xms)
6 方法区内部结构
6.1 方法区之间到底存储了什么
- 主要用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、字段信息),常量,静态变量,即时编译器编译后的代码缓存等(经典版本)
6.1.2 类型信息
1 . 这个类型的完整有效名称
2 . 这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类)
3 . 这个类型的修饰符
4 . 这个类型直接接口的一个有序列表
6.1.3 域信息(属性)
1 . jvm必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
2 . 域的相关信息包括:域名称,域类型,域修饰符
6.1.4 方法信息
1 . 方法名称
2 . 方法返回类型
-
方法参数数量和类型
-
方法修饰符
-
方法字节码,操作数栈,局部变量表大小
-
异常表
6.1.5 静态类变量
1 静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分
2 类变量被类的所有实例共享,即使没有类实例时你也可以访问。
6.1.6 全局常量
被声明为final的类变量处理方法不同,每个全局常量在编译时就会被分配了。
代码
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-06-17 09:41
*/
public class Test11 {
public static int i=1;
public static final int i1 =2;
}
使用javap -v -p
命令查看字节码文件
6.1.7 运行时常量池
- 运行时常量池与常量池
字节码文件包含了常量池,经过类加载器系统加载到方法区后,就会变成运行时常量池。
- 常量池
一个有效的字节码文件中除了包含类的版本信息,字段,方法以及接口等描述信息外,还包含一项信息就是常量池表包括各种字面量和对类型,域和方法的符号引用
主要包括:
-
数量值
-
字符串值
-
类引用
-
字段引用
-
方法引用
-
为什么要有常量池?
java中的字节码文件需要数据支持,通常这中数据会很大以至于不能直接存到字节码里,换另一种方式可以存到常量池中,这个字节码包含指向常量池的引用。
-
运行时常量池
-
运行时常量池是方法区的一部分
-
常量池表是Class文件的一部分,用于存储编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
-
运行时常量池包含了编译期就已经确定的数值字面量,也包括了运行期解析后才能获得的方法或字段引用。此时已不再是常量池中的符号地址,这里换为真实地址
-
当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大值,则会抛出oom
-
6.2 方法区的演进细节
- 首先明确:只有hotspot才有永久代
6.2.1 jdk1.6及以前
有永久代,静态变量存放在永久代上
6.2.2 jdk1.7
有永久代,但已经逐步“去永久代”,字符串常量池,静态变量移除,保存在堆中
6.2.3 jdk1.8及以后
无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池,静态变量仍在堆
6.2.4 永久代为什么会被元空间给替代
-
为永久代设置空间大小是很难确定的
-
对永久代进行调优是很困难的
7 方法区的垃圾收集
垃圾回收主要回收的是常量池中废弃的常量和不再使用的类型。
方法区内常量池中主要存放两大常量:字面量和符号引用。
字面量:
-
文本字符串
-
被声明为final的常量值
而符号引用属于编译原理概念:
-
类和接口的全限定名
-
字段的名称和描述符
-
方法的名称和描述符
只要常量池中常量被任何地方引用,就可以回收
而判断一个类型是否属于“不再被使用的类”条件就比较苛刻需要同时满足
-
该类的所有实例都已经被回收
-
加载该类的类加载器已经被回收
-
该类对象的java.lang.Class 对象没有在任何地方被引用。