类加载机制
虚拟机类加载机制:虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行验证、解析、初始化,最终形成可以被虚拟机直接使用的java类型
类的生命周期:加载---->验证----->准备------>解析------>初始化----->使用---->卸载
验证到解析这三阶段有叫连接;
加载到初始化阶段都是在程序运行期间完成的;
加载、验证、准备、初始化、使用、卸载这几个过程顺序是确定的,解析阶段则不一定;可能在初始化之后再进行,这是为了支持java动态绑定或者晚期绑定;
1、加载阶段
负责查找并且加载类的二进制数据文件,其实就是将class文件中的二进制数据读取到内存中,然后将该字节代表的静态存储结构转换为方法区中运行时的数据结构,并在对内存中生成一个该类的java.lang.Class对象,作为访问方法区数据结构的入口;
2、连接阶段
(一)验证
确保Class文件的字节流符合jVM规范
- 文件格式验证
-
在二进制文件中,头部都存在魔术因子,因子决定着该文件是什么类型,class文件的魔术因子是0xCAFEBABE
-
. 主次版本号
编译成class的JDK版本与JVM规范版本是否兼容; -
. class文件字节流是否存在残缺
…
- 元数据验证
- 检查该类是否存在父类或者实现接口,父类或者接口是否合法,是否真实存在
- 该类是否继承了被final修饰的类,final修饰的类不允许被继承
- 该类是否为抽象类,若继承了抽象类或者实现了接口,是否实现了这些抽象方法
- 检查重载方法是否合法
1.字节码验证
(二)准备阶段
为静态变量分配内存以及为给定类型静态变量指定默认值;
如:
class A{
private static int a=10;
private static final b=3;
}
在准备阶段,a的值时int类型的默认值0而不是10;而b在该阶段是3,因为静态变量(可以直接计算出结果)不会被导致类的初始化,是一种被动引用,因此就不存在连接阶段了;
(三)解析阶段
在常量池中寻找类、接口、字段、方法的符号引用,然后将这些符号引用替换成直接引用;
2、初始化阶段
当一个类首次使用时才会被初始化;
最主要的的一件事情就是执行方法的的过程(clinit是class init的简写),在该方法中所有的类变量会被赋予编写程序制定的值,包含了所有静态变量赋值操作和静态代码块的执行;
方法在编译期生成的,并不是每个类都会生成方法,比如某个类没有静态代码块、类变量,也就没有生成该方法的必要了;
类的主动使用和被动使用
主动使用会导致类的初始化,被动引用不会导致的类的加载和初始化
1、主动使用:
- 通过new关键字会导致类的初始化;
- 访问类的静态变量,包括读取和更新会导致类的初始化;
- 访问类的静态方法,会导致类的初始化
- 对类进行反射操作,会导致类的初始化;
- 初始化子类(new、调静态方法、静态变量、反射…)会导父类的初始化(注意:通过子类调用父类的静态变量自会导致父类初始化,不会初始化子类)
- 执行启动类,即执行main函数,会导致该函数所在的类初始化;
除了以上6中情况,其他都是被动使用;
2. 被动使用:
-
构造某个类的数组,不会导致该类初始化(该方式只是在对内存中开辟一段连续的地址空间);
-
引用类的静态常量不会导致类的初始化
//1、不会导致类初始化 public static final int MAX=10; //2、这种方式仍然会导致类初始化 public static final int MIN=New Random().nextInt();
因为Random是需要进行随机函数计算的,在类的加载、连接阶段是无法进行计算的,需要初始化后才能对其赋与准确的值
绑定概念
这里对静态(前期)绑定和动态(晚期)绑定做一个解释:
绑定:指一个方法的调用与方法所在的类(方法主体)关联起来;
静态绑定:在程序执行前方法已经被绑定(编译过程中已经知道这个方法属于哪个类的方法);在java中只有static、final、private、构造方法是前期绑定;
对于private的方法,首先一点它不能被继承,既然不能被继承那么就没办法通过它子类的对象来调用,而只能通过这个类自身的对象来调用。因此就可以说private方法和定义这个方法的类绑定在了一起。
final方法虽然可以被继承,但不能被重写(覆盖),虽然子类对象可以调用,但是调用的都是父类中所定义的那个final方法,(由此我们可以知道将方法声明为final类型,一是为了防止方法被覆盖,二是为了有效地关闭java中的动态绑定)。
构造方法也是不能被继承的
动态绑定:在运行期间根据具体对象的类型进行绑定;