类的生命周期
类初始化时机
触发类初始化的情况
- new创建对象,访问类的静态字段(final修饰的常量字段除外),调用类的静态方法
- 反射API调用类时
子类初始化触发父类初始化
对于接口而言,接口有default方法的,会初始化接口
虚拟机启动指定的主类
-
不触发类初始化的特殊情况说明
通过子类引用父类的静态字段,不会触发子类的初始化
- 通过数组定义来引用类,不会触发类的初始化
- 类的常量不会触发类的初始化
常量在编译阶段存入类的常量池中,本质上并没有直接引用到定义常量的类
类加载过程
1.加载
步骤:
- 通过类的全限定名获取类的二进制字节流
- 将二进制字节流转化为方法区的运行时数据结构
- 内存中生成对应的java.lang.Class对象,存放到方法区中
要点:
非数组类由类加载器加载,数组类由虚拟机直接创建
数组类中的元素类型是引用类型,在加载元素类型时,数组类会被标识为与元素类型对应的类加载器关联
数组类中的元素类型是基本类型,数组类会被标识为与启动类加载器关联2.验证
确保class文件的字节流信息符合虚拟机的要求,保证虚拟机运行时的安全文件格式验证
基于二进制字节流进行验证,验证版本号,文件的魔数,常量池中常量的类型等元数据验证
对字节码描述的信息进行语义分析,保证不存在不符合java语言规范的元数据信息,校验是否有父类,是否允许继承,方法实现,字段是否合理字节码验证
通过数据流和控制流分析,确定程序语义的合法性且符合逻辑。主要针对类的方法体校验分析。符号引用验证
连接阶段解析过程中发生的验证,即发生在虚拟机将符号引用转换为直接引用的时候,确保解析动作能正确执行3.准备
为类变量分配内存并为类变量设置零值,都在方法区分配内存,如果类变量为常量,初始化为指定值4.解析
虚拟机将常量池中的符号引用解析成直接引用的过程。符号引用: 用一组符号描述所引用的目标,类似方法签名,通过符号引用唯一确定需要引用的类,引用的方法,引用方法的参数类型和返回类型 直接引用: 直接指向目标的指针,类似内存地址
5.初始化
执行类构造器
类加载器
类加载器用于实现类的加载动作
类是否“相等”取决于类的全限定名和加载类的类加载器是否一样
两种类加载器:
- 启动类加载器(Bootstrap ClassLoader),使用C++实现,虚拟机自身一部分
- 所有其他的类加载器,java语言实现,继承自抽象类ClassLoader
包括扩展类加载器(Extension ClassLoader)【ExtClassLoader】,
应用程序类加载器(Application ClassLoader)【AppClassLoader】,
其他用户自定义类加载器
双亲委派模型:
一个类加载器收到了类的加载请求,自己先不加载,请求委派给父类加载器去完成,每个层次的类加载器都是如此,最终都传送到顶层启动类加载器中,当父类的加载器无法完成加载请求,子类加载器才尝试自己去加载
- 保证核心类库不被篡改
- 防止类的重复加载
具体实现:
实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中
- 先通过方法findLoadedClass检查类是否加载过
- 没有加载则调用父加载器的loadClass方法,若父加载器为空则使用启动类加载器作为父加载器
- 父加载器加载失败,再调用自己的findClass方法进行加载