这篇博客将以类加载执行顺序为探索的起点,串讲涉及到的Java相关知识,主要是这个流程中JVN内部运行机制的解析。
结论
注解:
默认该类及其父类JVM未曾加载过
先父后子,先静后常再构造
同等级内代码逐条按顺序执行
* 当静态代码和非静态代码中成员变量包含对象,也会先执行该对象类的静态代码和构造函数
先修知识
JVN 运行时数据区
JVN内存可简单分为三个区:堆(heap)、栈(stack)和方法区(method):
堆区
存放对象本身(包括非static成员变量),所有线程共享栈区
存放基础数据类型、对象的引用,每个线程独立空间,不可互相访问
栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)- 方法区(静态区,包含常量池)
存永远唯一元素(类-class、类变量-static变量、方法),所有线程共享
实例化一个对象的执行顺序分析
因为涉及到的情况可以很复杂,最复杂的情况可能是有多个父类,父类及子类中有多个成员变量对象,这种情况下调用栈会比较深,所以我们先重简单一个单独的类开始分析,而且只要明白了基础的过程,那么再复杂的情况也只是在这个基础上叠加调用。
一个单独的类的情况
有父类存在的情况
有父才有子,所以当父类存在时(其实Object类是所有类的父类),只是在这两个阶段前(执行静态、执行非静态)前,先运行父类的相关代码
默认该类及其父类JVM未曾加载过
此处为什么要执行父类的构造方法呢?(此处存疑,待查证,欢迎指正)
因为子类的构造方法默认调用super();
创建子类实例的时,如果没有super调用父类带参数的构造方法,则默认会调用父类的无参构造方法,默认调用super().
当类中包含属性类时
因为同等级内代码逐条按顺序执行,所以当存在属性类时,例如:
public class A {
static {
System.out.println("A static");
}
B b = new B();
{
System.out.println("A not static");
}
A(){
System.out.println("A constructor");
}
public static void main(String[] args) {
System.out.println("Hello World!");
A m = new A();
}
}
class B{
static {
System.out.println("B static");
}
{
System.out.println("B not static");
}
B(){
System.out.println("B constructor");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
结果输出如下:
A static
Hello World!
B static
B not static
B constructor
A not static
A constructor
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在实例化A类时,在顺序执行到非静态代码中的“B b = new B()”时,将先执行加载完毕B的所有代码;
如果我将代码改成“非静态代码块”在“实例化B代码”前,如下:
...
{
System.out.println("A not static");
}
B b = new B();
...
- 1
- 2
- 3
- 4
- 5
- 6
将会得到如下输出
A static
Hello World!
A not static//这一行也在前面了
B static
B not static
B constructor
A constructor
- 1
- 2
- 3
- 4
- 5
- 6
- 7
得以验证:同等级内代码逐条按顺序执行,遇到属性类则加载完属性类后再执行下一步代码
注意:当属性类不立刻进行实例化时,JVM只会将堆中的成员属性类指向一个null,而不进行加载该属性类的操作
例:
...
B b;
...
- 1
- 2
- 3
代码如上时,将不会有任何与类B相关的输出
关联知识
类变量-static变量、类方法-static方法
这两者的特性:
- 全局唯一,一改都改,节省资源
- 可直接通过类名调用
- 仅能访问static数据、static方法
- 不能引用this和super
下面我们通过实例化对象执行步骤来推导出以上四条特性:
由流程图可知,
1. static代码只在类初始化时加载一次,加载后存在方法区,而每一个对象在实例化时,只是在堆中保存指向方法区的引用,所以全局唯一,一改都改,节省资源
2. 因为static代码在对象被实例化之前和类初始化一起执行,所以除了可以通过对象应用外,也可以直接通过类名引用
3. 因为先执行静态代码,再执行非静态代码,所以static代码仅能访问static数据、static方法
4. this、super属于非静态代码(??),所以不能引用this和super