类加载时机
生命周期
加载 -> 验证 -> 准备 -> 解析 -> 初始化 -> 使用 -> 卸载
- 类初始化的四种情况
- 遇到new、getstatic、putstatic或invokestatic这四条字节码指令的时候
- 使用java.lang.reflect进行反射调用的时候
- 一个类初始化的时候,如果父类还没有进行初始化
- 当虚拟机启动时,用户需要制定一个要执行的主类,虚拟机会初始化这个类
类加载过程
加载
在加载阶段虚拟机需要完成以下三件事
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态储存结构转化为方法区运行时数据结构
- 在Java堆中声称一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
验证
验证是为了确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会对虚拟机产生危害,主要有以下过程
-
文件格式校验: 是否符合class文件的格式,并能被当前版本的虚拟机处理,验证点如下
- 是否已魔数0xCAFEBABE开头
- 主、次版本号是否能被当前虚拟机处理
- 常量池中是否有不被支持的常量类型
- 指向常量的索引是否有指向不存在或不符合类型的常量
- CONSTANT_Utf8_info是否有不符合UTF8的编码数据
- class文件中各个部分及文件本身是否有被删除或附加的其它信息
-
元数据验证: 对字节码描述进行语义分析,以保证其描述的信息符合Java规范主要包括以下验证点
- 这个类是否有父类
- 这个类是否继承了不能被继承的类
- 如果这个类不是抽象类,是否实现了其父类或接口之中要求的所有方法
- 类中的字段和方法是否与父类产生矛盾
-
字节码验证:进行数据流和控制流分析,保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为
- 保证操作数据栈的数据类型与指令码序列都能配合工作
- 保证跳转指令不会跳转到方法体以外的字节码指令上
- 保证方法体重的类型转换时有效的
-
符号引用验证: 对类自身以外的信息进行匹配性校验
- 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定的类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
- 符号引用中的类、字段和方法的访问性是否可以被当前类访问
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配,这时候分配的类变量,不包括实例变量,实例变量将会在类初始化的时候一起分配到Java堆中。但是,如果类字段的字段属性在ConstantValue中存在,那么在准备阶段就会陪变量赋值。
解析
解析是虚拟机讲常量池内的符号引用替换为直接引用的过程
- 符号引用: 以一组符号来描述引用的目标,可以是任意字面量
- 直接引用: 直接引用是可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄
解析主要针对类或接口、字段、类方法、接口方法等四类符号进行引用,分别对应常量池的CONSTANT_Class_info、Constant_Fieldref_info、CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info
初始化
初始化变量和其它资源,是执行构造器方法<cInit>()的过程
- <cInit>()是由编译器自动收集类中所有变量的赋值动作和静态语句块(static)中的语句合并产生的,顺序是先变量赋值,再静态语句块
- <cInit>()方法与类的构造函数不同,它不需要显示的调用父类的构造器,虚拟机会保证子类的<cInit>()会在父类的<cInit>()方法执行完毕后执行
- 由于父类的<cInit>()先执行,父类的静态代码块先于子类的变量赋值操作
- <cInit>()对于类或接口不是必须的,如果没有静态语句块或者对变量的赋值操作,有可能不会生成<cInit>()方法
- 执行接口的<cInit>()方法不需要先执行父接口的<cInit>(),只有父接口的变量被使用时才会初始化父接口
- 虚拟机会保证<cInit>()调用的线程安全,只有一个线程回去调用类的<cInit>()方法,其它线程会阻塞等待
类加载器
双亲委派模型
Java虚拟机理论上存在两种类
- 启动器加载器(Bootstrap ClassLoader): 虚拟机的一部分,由c++实现
- 其它类的加载器: 继承自java.lang.ClassLoader
绝大多数Java程序分为下面三种加载器
- 启动类加载器(Bootstrap ClassLoader): 负责加载<JAVA_HOME>/lib中的jar
- 扩展类加载器(Extension ClassLoader): 负责加载<JAVA_HOME>/lib/ext中的jar
- 应用程序加载器(Application ClassLoader)
ps: Android的ClassLoader默认只有BootClassLoader和PathClassLoader,PathClassLoader与Application ClassLoader类似,都是加载程序中的类的加载器,具体参考这里
双亲委派的优点
- Java类随着加载器具备了一种带有优先级的层次关系
- 安全,避免系统中同名的类被恶意代码替换
参考
- 深入理解Java虚拟机, 周志明
- Android插件化实践(2)—ClassLoader