类的加载过程
类的加载过程:加载,验证
,-->准备
-->解析
-->初始化,-->使用-->卸载。
加载阶段
加载,就是将 Java 类的字节码文件加载到计算机内存中,并在内存中构建出 类模板对象。
所谓类模板对象,其实就是 在 JVM 内存中的一个快照,JVM 将从字节码文件中解析出的常量池、类字段、类方法等信息存储到模板中,这样 JVM 在运行期便能通过类模板而获取 Java 类中的任意信息,能够对 Java 类的成员变量进行遍历,也能进行 Java 方法的调用
加载阶段,简言之,查找并加载类的二进制数据,生成 Class 的实例
在加载类时,Java 虚拟机完成以下事情:
通过类的全名,获取类的二进制数据流
解析类的二进制数据流为方法区内的数据结构
创建 .Class 类的实例,表示该类型。作为方法区这个类的各种数据的访问入口
链接阶段
1:验证
当类加载完后,开始链接操作首先是验证
验证的目的是保证加载的字节码是合法、合理并符合规范的如有修改或错误验证不通过
验证的步骤比较复杂,大体上 需要做以下检查
格式检查: 版本检查,长度检查
语义检查: 检查 是否继承final,是否有父类,是否是抽象类,抽象方法是否实现
字节码验证: 操作数类型是否合理
符号引用验证: 符号引用的直接引用是否存在
整体说明:
验证的内容则涵盖了类数据信息的格式验证、语义检查、字节码验证,以及符号引用验证等
其中格式验证会和加载阶段一起执行。验证通过之后,类加载器才会成功将类的二进制数据信息加载到方法区中
格式验证之外的验证操作将会在方法区中进行
链接阶段的验证虽然拖慢了加载速度,但是它避免了在字节码运行时还需要进行各种检查
Java 虚拟机会进行字节码的语义检查,但凡在语义上不符合规范的,虚拟机也不会给予验证通过。比如:
- 是否所有的类都有父类的存在(在 Java 里,除了 Object 外,其他类都应该有父类)
- 是否一些被定义为 final 的方法或者类被重写或继承了
- 非抽象类是否实现了所有抽象方法或者接口方法
- 是否存在不兼容的方法(比如方法的签名除了返回值不同,其他都一样,这种方法会让虚拟机无从
2:准备
,简言之,为类的静态变量分配内存,并将其初始化为默认值
当一个类验证通过时,虚拟机就会进入准备阶段。在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置默认初始值。
3:解析
解析阶段,将类、接口、字段和方法的符号引用转为直接引用
直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
符号引用:一组符号来描述目标,可以是任何字面量。属于编译原理方面的概念如:包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
解析主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符这7类符号引用进行。
初始化阶段
为类的静态变量赋初值。
特别注意:类在初始化前,必须先经过加载、验证、准备阶段
。
静态变量赋初值的两种方式
声明静态变量同时指定初始值。
private static String x="123";
静态代码块里为静态变量赋值
private static String x;
static{
x="123";
}
类初始化的注意事项:
类只初始化一次;
初始化的执行顺序(静态变量、静态代码块)只跟代码中出现的顺序有关。
类初始化的触发条件
- 创建类的实例,也就是new一个对象;
- 获取或者赋值类的静态变量、静态非字面值常量(静态字面值常量除外);
- 调用类的静态方法;
- 反射调用
- 初始化一个类的子类(会首先初始化子类的父类)
- 启动程序所使用的main方法所在类。
类初始化的步骤
如果这个类还没有被加载和链接,那先进行加载和链接.
如果这个类存在直接父类,并且这个直接父类还没有被初始化,先初始化直接父类.
注意:在一个类加载器中,类只能初始化一次。
如果类中存static变量和static代码块,优先执行这些初始化语句。
类的加载生命周期
以下几种情况将结束类加载的生命周期:
- 执行了System.exit()方法
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
对象创建的内存变化
对象在内存中的位置:对象由new关键字创建,如同数组,实体存在于堆内存中.任何事物均可以定义成类,创建对象,属于引用类型.而对象的引用变量是一个普通变量。存储的值是该对象堆内存中的地址.
创建对象表达式
创建对象的内存变化
数组内存变化图