方法区的理解
方法区可以看作是独立于Java堆的内存空间
- 方法区与Java堆一样,是各个线程共享的内存区域
- 方法区在JVM启动时被创建,实际物理内存与堆一样是可以不连续的
- 方法区的大小与堆一样是可以选择固定大小或者扩建的
- 方法区的大小决定了系统可以保存多少类
- 关闭JVM就会释放这个区域的内存
JDK7及之前:java.lang.OutOfMemoryError:PermGen [永久代]
JDK8及之后:java.lang.OutOfMemoryError:Metaspae [元空间]
方法区的演进
栈,堆,方法区之间的交互
Person person = new Person()
方法区 Java栈 Java堆
设置方法区大小与OOM
元数据区大小可以使用:
-XX:MetaspaceSizeh
-XX:MaxMetaspaceSize
默认值依赖于平台 默认分别为 21M -1(无限制)
解决异常
要解决OOM异常或者heap space异常一般是首先通过内存映像分析工具对dump出来的堆转储快照进行分析
重点是确认内存中的对象是否是必要的也就是要先分清楚到底是出现了内存泄露还是内存溢出
若是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链,可以准确的定位泄露代码的位置
若不存在内存泄露,也就是内存中的对象确实都还必须存活着,那就应对检查虚拟机的堆参数(-Xmx,-Xms)
方法区内部结构
方法区中存储的内容:
-
类型信息(域信息、方法信息)
-
运行时常量池
类型信息:
对每个加载的类型(类,接口,枚举,注解)JVM必须在方法区中存储以下类型信息
- 这个类型完整有效名称(类名,包名)
- 直接父类有效名
- 修饰符
- 直接接口的一个有序列表
域信息:
- 保持类型所有域的相关信息以及相关说名
- 域相关信息包括:域名,域类型,域修饰符
方法信息:
- 方法名
- 返回类型
- 参数的数量和类型
- 方法修饰符
- 方法字节码,操作数栈,局部变量表以及大小
运行时常量池vs常量池
方法区中,内部包含了运行时常量池
字节码文件中,内部包含了常量池
常量池:存放编译期间生成的各种字面量与符号引用
运行时常量池:常量池表在运行时的表现形式
编译后的字节码文件中包含了类型信息、域信息、方法信息等。通过ClassLoader将字节码文件的常量池中的信息加载到内存中,存储在了方法区的运行时常量池中。
理解为字节码中的常量池Constant pool只是文件信息,它想要执行就必须加载到内存中。而Java程序是靠JVM,更具体的来说是JVM的执行引擎来解释执行的。执行引擎在运行时常量池中取数据,被加载的字节码常量池中的信息是放到了方法区的运行时常量池中。