Java类初始化条件
(1)类加载的时机:
a) 生命周期:加载,验证,准备,解析,初始化,使用,卸载。
b) 5个确定的加载顺序,验证,准备,初始化,卸载。
c) 初始化的条件(有且仅有的四个条件):
i. 遇到new,getstatic,putstatic,invokestatic字节码时,若未初始化,触发。
ii. 反射调用的时候,若没有初始化,触发;
iii. 初始化类时,若父类未初始化,触发;
iv. 虚拟机启动是,主类(含有main方法)触发初始化;
要点:
(1)子类中调用父类的静态字段,只会触发父类的初始化,不会触发子类的初始化;
(2)New一个类的数组的时候,不会触发类的初始化阶段;
(3)类调用static final常量,不会触发类的初始化。
特列:
接口的初始化和类的初始化稍有不同,接口中没有static代码块。区别在于类初始化条件第三条的父类初始化,若是接口则不需要初始化接口的时候初始化父类接口,只有当引用父类的时候才会进行初始化。
Java类加载过程
(1)加载:
a) 根据类的权限定名获取二进制字节流;
b) 将上述动作获取的字节流中代表的静态存储结果转化为运行是数据结构;
c) 在java堆中生成一个class对象,作为数据访问入口。
(2)验证(确认字节流信息符合虚拟机要求):
a) 文件格式验证:确保获取的二进制字节流能够正确解析,并存储于方法区中;
b) 元数据验证:进行语义分析,确保符合java语言规范;
c) 字节码验证:主要进行数据流和控制流分析;
d) 符号引用验证:确保解析阶段正常运行,若无法通过验证,抛出异常。
(3)准备(正式为变量分配内存并设置类变量初始值的阶段)
初始化的部分只是变量,通常情况下都为零值。
如果具有final修饰,则初始化为指定的值。
(4)解析
将常量池里面的符号引用转化为直接引用的过程
a) 类或者接口的解析:
i. 若不是数组类型,传递全限定名给当前类的加载器;
ii. 若是数组类型,并且为对象元素,类似Integer类,则按照第一点加载元素类型,若是integer类,直接生成维度堆
iii. 上述步骤没有异常,则进行符号引用验证,确认访问权限。
b) 字段解析:
ⅰ. 本身具有简单名和字段描述都与目标相匹配的字段,则返回;
ⅱ.否则,若是有接口实现,从上往下递归搜索,找到则返回;
ⅲ.否则,按照继承关系,从上往下递归搜索,找到则返回;否则抛出异常;
c) 类方法解析
和字段解释稍微有点不同的地方是,找方法时先找父类,而不是接口。其他逻辑就是找到匹配的则返回。这里要判断本身是否为抽象类,因为匹配到的方法可能是实现的接口列表以及父接口的方法;否则NOsuchmethod异常
d) 接口方法解析
与类方法的过程是一样的。区别就是不需要判定是不是实现过该方法。
(5)初始化
这个阶段是开始执行java代码的时候。这个阶段是执行构造函数的过程。
Java类加载器
Java里面的三类加载器:启动类加载器,扩展类加载器,应用程序类加载器。推荐使用的双亲委派模型。每一次的加载都要求先把这个加载请求发到父类加载器进行加载,所有的加载请求最终都会传给启动类加载器,只有当父类加载器反馈回来不能完成加载请求,则尝试自己加载。
OSGI 则是破坏双亲委派模型的案例之一。