一、类加载的时机
类加载过程的第第一阶段:“加载” 对于 “加载” 的这个过程开始的情况虚拟机并没有进行约束,而是由虚拟机进行自由把握的。
但是对于以下五种情况立即进行对类 "初始化” 的过程:
- 使用new实例化对象,读取或调用静态对象,调用静态方法。
- 使用Java.lang.reflect包下的方法对类进行反射调用
- 初始化一个类时,先对其父类进行初始化
- 虚拟机启动时先初始化主函数所在的类
以上五种情况触发的初始化情况称为主动引用。而除了主动引用的情况引用类的情况都被称为被动引用,被动引用不会触发初始化。
三种被动引用举例:
- 通过子类引用父类的静态字段
子类并不会进行初始化。父类调用了静态字段,父类进行初始化。 - 通过数组定义引用类
数组类进行初始化,引用类不进行初始化。 - 引用某类中的常量
常量存入了常量池,并不会进行初始化。
并且存入的是调用常量的那个类的常量池。在编译阶段通过常量传播优化。
二、类加载的过程
类的生命周期:加载、验证、准备、解析、初始化、使用、卸载。
类的加载过程:加载、验证、准备、解析、初始化。
验证、准备、解析。三个过程为连接阶段。
除了解析阶段其他阶段的执行位置是固定的。
解析阶段可以在初始化阶段之后进行。
-
加载
1.1 通过一个类的全限定名来获取定义此类的二进制字节流(类加载器)
1.2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
1.3 在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类各种数据的访问入口 -
验证
验证阶段是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
2.1 文件格式验证
验证字节流是否符合Class文件格式的规范。例如是否以魔数0xCAFEBABE开头
2.2 元数据验证
主要目的是对类的元数据信息进行语义校验。例如,这个类的父类是否不允许被继承(final类)
2.3 字节码验证
整个验证阶段最为复杂的阶段,通过分析数据流、字节流 确定程序语义是否合法,符合逻辑。主要对类里面的方法体进行分析。例如,方法体中类型转换是有效的。跳转指令不会跳转到方法体以外的字节码指令上。 -
准备
正式为类变量分配内存,并设置类变量初始值。
此时的内存分配仅包括类变量,实例变量将会在对象初始化时随着对象一起被分配在Java堆中。(类变量在运行时存入在方法区中)
类变量在准备阶段过后的初始值为 0 -
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
4.1 类或接口解析
虚拟机将一个全限定类名传递给所在的类加载器去接在这个类。
4.2 字段解析
4.3 类方法解析
4.4 接口方法解析 -
初始化
初始化阶段是执行类构造器()方法的过程(可以类比对象的初始化,调用构造函数()方法 )
()方法是由编译器自动收集类中的所有类变量的赋值动作,和静态语句块中的语句合并产生的。
三、类加载器
-
类与类加载器
在加载的阶段中 “通过一个类的全限定名来获取此类的二进制字节流” 这个动作放在java虚拟机外部去实现。由应用程序自己决定如何获取这个类。实现这个动作的代码模块称为“类加载器”。
两个类是否 “相等” 的前提是,由同一个类加载器加载。 -
双亲委派模型
2.1 启动类加载器
用C++语言实现,是虚拟机自身的一部分。
加载<java_home>/libt目录中的类库
无法被java程序直接引用。
2.2 扩展类加载器
加载<java_home>/lib/ext目录中的类库
开发者可以直接使用扩展类加载器
2.3 应用程序类加载器(系统类加载器)
它负责加载用户类路径上所指定的类库,
开发者可以直接使用这个类加载器
如果没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器
如下图这种,类加载器之间的层次关系,称为双亲委派模型。
双亲委派模型的工作过程:如果类加载器收到类加载请求,首先不会自己去加载这个类。而是把这个请求委派给父类加载器去完成。每个层次的类加载器都是如此。所以最终请求都应该传送到启动类加载器中。当父类加载器无法完成这个请求时,子类加载才会尝试自己去加载。
双亲委派模型的优点:比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象。
保证了类库的安全性,缺点就是效率低。