WangScaler: 一个用心创做的做者。html
声明:才疏学浅,若有错误,恳请指正。java
java类的生命周期包括加载、链接(验证、准备、解析)、初始化、使用、卸载五个阶段。而解析阶段会在初始化以前或以后触发。类的加载不是随着jvm的启动而加载,而是随着使用动态的加载。缓存
接下来咱们首先了解下虚拟机的类加载机制。安全
咱们知道java的优点之一就是跨平台性,为何java能跨平台执行呢?就由于java是运行在java虚拟机jvm上的。那么jvm的类加载机制是怎样的呢?咱们知道java编译以后的文件是class文件,而虚拟机的类加载机制就是把Class文件加载到内存,进行校验,解析和初始化的过程。markdown
加载就是经过类的全限定名来获取到class文件,将文件的二进制字节流转化成方法区的静态数据结构,而后在内存中生成这个类的class对象并在堆中生成一个便于用户调用的class类型的对象。数据结构
验证就是对文件格式、元数据、字节码进行验证(即语法语义的验证)、符号引用的验证,确保Class文件中的字节流不会危害虚拟机的安全。可参考java虚拟机符号引用验证_深刻了解Java虚拟机---虚拟机类加载机制。app
给静态变量赋初值0。jdk8以前类的元信息、常量池、静态变量都是存储在永久代(方法区),而jdk8以后元空间(方法区)替代了永久代只存储类的元信息,将常量池和静态变量转移至堆内存中。jvm
将符号引用替换成直接引用。解析阶段会在初始化以前或以后触发。ide
一、假如A引用B(具体的实现类),编译阶段编译A的时候,是没法知道B是否被编译的,因此编译阶段B会被符号所代替,这个符号就是B的地址。在解析的时候若是B还没有加载,就会加载B,此时A中的符号将替换成真正的B的地址,这种称为静态解析,此时的解析是在初始化以前发生。oop
二、若是A引用的是B的抽象方法或者接口。那么只有在调用A的时候才知道具体的实现类是哪个。此时的解析是发生在初始化以后的,也被成为动态解析。
三、虚拟机能够对第一次的解析结果进行缓存,避免解析动做的重复执行。
类、对象的初始化顺序:(静态变量、静态代码块)>(变量、代码块)>构造器。
下面以简短的例子来演示初始化的过程。
package com.wangscaler.load;
/** * @author WangScaler * @date 2021/7/28 19:17 */
public class Father {
private int i = method();
private static int j = staticMethod();
static {
System.out.println("一、父类静态代码块");
}
Father() {
System.out.println("二、父类构造器");
}
{
System.out.println("三、父类代码块");
}
private int method() {
System.out.println("四、父类方法");
return 1;
}
private static int staticMethod() {
System.out.println("五、父类静态方法");
return 1;
}
}
复制代码
package com.wangscaler.load;
/** * @author WangScaler * @date 2021/7/29 14:15 */
public class Son extends Father {
private int i = method();
private static int j = staticMethod();
static {
System.out.println("六、子类静态代码块");
}
Son() {
System.out.println("七、子类构造器");
}
{
System.out.println("八、子类代码块");
}
public int method() {
System.out.println("九、子类方法");
return 2;
}
private static int staticMethod() {
System.out.println("十、子类静态方法");
return 2;
}
public static void main(String[] args) {
}
}
复制代码
例子如上,咱们执行上述的代码,main方法里面什么都没有,会有打印产生吗?
执行结果以下:
五、父类静态方法
一、父类静态代码块
十、子类静态方法
六、子类静态代码块
复制代码
为何main方法里面什么都没有也会打印呢?
由于
一、main方法所在的类优先加载并初始化,固会加载初始化Son这个类。
二、Son继承自Father,因此又会优先初始化Father这个类。
三、Father从上依次往下初始化静态变量和静态代码块。
一、父类静态代码块
四、Father加载完以后,加载Son,依然是从上往下依次初始化静态变量和静态代码块
六、子类静态代码块
修改main方法,以下:
public static void main(String[] args) {
Son son = new Son();
}
复制代码
执行结果以下:
五、父类静态方法
一、父类静态代码块
十、子类静态方法
六、子类静态代码块
四、父类方法
三、父类代码块
二、父类构造器
九、子类方法
八、子类代码块
七、子类构造器
复制代码
前四个是毋庸置疑的,那么main方法建立对象(new)时,此时是实例初始化。JVM为每个类的每个构造方法都建立一个()方法,用于初始化实例变量,由虚拟机自行调用。
执行方法,首行是super(),因此执行父类的方法。
从上至下执行非静态变量、非静态代码块
三、父类代码块
执行完父类,继续执行Son
八、子类代码块
修改main方法
public static void main(String[] args) {
Son son = new Son();
System.out.println("---------------------------wangscaler-----------------------------------");
Son son1 =new Son();
}
}
复制代码
打印以下
五、父类静态方法
一、父类静态代码块
十、子类静态方法
六、子类静态代码块
四、父类方法
三、父类代码块
二、父类构造器
九、子类方法
八、子类代码块
七、子类构造器
------------------------------wangscaler--------------------------------
四、父类方法
三、父类代码块
二、父类构造器
九、子类方法
八、子类代码块
七、子类构造器
复制代码
由上能够看出,多个实例就有多个方法,执行过程同实例初始化,就不过多介绍。
咱们知道final、private修饰的方法和静态方法不能被子类重写。因而咱们在实例初始化的代码状况下修改Father。
public int method() {
System.out.println("四、父类方法");
return 1;
}
复制代码
固然子类的method上须要添加注解@Override,由于此时的子类变成了重写父类的method方法。
此次的执行结果是:
五、父类静态方法
一、父类静态代码块
十、子类静态方法
六、子类静态代码块
九、子类方法
三、父类代码块
二、父类构造器
九、子类方法
八、子类代码块
七、子类构造器
复制代码
与实例初始化状况不一样的是,第五个打印语句,为何呢?
由于在执行父类的方法的时候,当初始化非静态变量i时,调用方法this.method(),而this指得是正在建立的对象Son,因此执行的是重写以后的method方法。
遇到new、getstatic、putstatic、或者invokestatic 这4条字节码指令,进行初始化。使用java.lang.reflect包的方法,对垒进行反射调用的时候,若是没有初始化,则先触发初始化。当使用JDK1.7的动态语言支持时,若是一个Java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic,REF_outStatic,REF_invokeStatic的方法句柄,而且这个方法句柄所对应的类没有进行过初始化,则须要先触发其初始化
类的初始化:
main方法所在类优先加载初始化
子类初始化时,优先初始化父类
类的初始化就是执行方法
实例的初始化:
类建立实例(new)初始化,执行方法
的首行是super(),对应父类的方法
由非静态变量、非静态代码块、构造器组成
非静态变量、非静态代码块从上至下依次执行、构造器最后执行
有几个构造器就有几个方法
重写的方法
注意:如下条件下没有方法:
- 没有初始化语句或静态初始化语句初始化;
- 仅包含static、 final修饰的类变量,而且类变量初始化语句是常量表达式;