①. 方法区的概述
①. 方法区在JVM启动的时候被创建,并且它的实际的物理内存空间和Java堆区一样都可以是不连续的 | 关闭Jvm就会释放这个区域的内存
②. 方法区时逻辑上是堆的一个组成部分,但是在不同虚拟机里头实现是不一样的,最典型的就是永久代(PermGen space)和元空间(Metaspace)
(注意:方法区时一种规范,而永久代和元空间是它的一种实现方式)
③. 方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:(java.lang.OutOfMemoryError:PermGen space、java.lang.OutOfMemoryError:Metaspace)
- 加载大量的第三方的jar包
- tomcat部署的工程过多(30-50个)
- 大量动态的生成反射类
- ④. 对于HotspotJVM而言,方法区还有一个别名叫非堆(Non-heap),目的就是要和堆分开,方法区可以看成一块独立于Java堆的内存空间
②. 方法区的内部结构
①. 深入理解Java虚拟机》书中对方法区存储内容描述如下:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等
②. 类型信息(对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必 .须在方法区中存储以下类型信息:
这个类型的完整有效名称(全名=包名.类名)
这个类型直接父类的完整有效名(对于interface或是java. lang.Object,都没有父类)
这个类型的修饰符(public, abstract, final的某个子集)
这个类型直接接口的一个有序列表
③. 域信息(成员变量)
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、 域类型、域修饰符(public, private, protected, static, final, volatile, transient的某个子集)
④. 方法信息:JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序
方法名称
方法的返回类型(或void)
方法参数的数量和类型(按顺序)
方法的修饰符(public, private, protected, static, final,synchronized, native , abstract的一个子集)
方法的字节码(bytecodes)、操作数栈、局部变量表及大小( abstract和native 方法除外)
异常表( abstract和native方法除外)
每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
⑤. non-final的类变量
(Order.class字节码文件,右键Open in Teminal打开控制台,使用javap -v -p
Order.class > tst.txt 将字节码文件反编译并输出为txt文件,可以看到被声明为static final
的常量number在编译的时候就被赋值了,这不同于没有被final修饰的static变量count是
在类加载的准备阶段被赋值为默认的初始化值,在初始化的时候赋予正确的初始化值
public static int count; descriptor: I flags: ACC_PUBLIC, ACC_STATIC public static final int number; descriptor: I flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL ConstantValue: int 2
以下代码不会报空指针异常
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!"); } }