实例化一个对象(类加载)的执行顺序详解

这篇博客将以类加载执行顺序为探索的起点,串讲涉及到的Java相关知识,主要是这个流程中JVN内部运行机制的解析。

结论

Created with Raphaël 2.1.0开始父类静态代码子类静态代码父类非静态代码父类构造函数子类非静态代码子类构造函数结束

注解:
默认该类及其父类JVM未曾加载过
先父后子,先静后常再构造
同等级内代码逐条按顺序执行
* 当静态代码和非静态代码中成员变量包含对象,也会先执行该对象类的静态代码和构造函数

先修知识

JVN 运行时数据区

JVN内存可简单分为三个区:堆(heap)、栈(stack)和方法区(method):

  • 堆区
    存放对象本身(包括非static成员变量),所有线程共享

  • 栈区
    存放基础数据类型、对象的引用,每个线程独立空间,不可互相访问
    栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)

  • 方法区(静态区,包含常量池)
    存永远唯一元素(类-class、类变量-static变量、方法),所有线程共享

实例化一个对象的执行顺序分析

因为涉及到的情况可以很复杂,最复杂的情况可能是有多个父类,父类及子类中有多个成员变量对象,这种情况下调用栈会比较深,所以我们先重简单一个单独的类开始分析,而且只要明白了基础的过程,那么再复杂的情况也只是在这个基础上叠加调用。

一个单独的类的情况

Created with Raphaël 2.1.0开始该类是否已加载到方法区中执行非静态代码1——在堆中为对象分配空间(对象信息:类引用、类变量引用、方法引用、成员变量等)执行非静态代码2——运行构造函数结束执行静态代码——加载该类的静态信息(类、static代码、方法)——在方法区分配空间yesno

有父类存在的情况

有父才有子,所以当父类存在时(其实Object类是所有类的父类),只是在这两个阶段前(执行静态、执行非静态)前,先运行父类的相关代码
默认该类及其父类JVM未曾加载过

Created with Raphaël 2.1.0开始父类静态代码子类静态代码父类非静态代码父类构造函数子类非静态代码子类构造函数结束

此处为什么要执行父类的构造方法呢?(此处存疑,待查证,欢迎指正)
因为子类的构造方法默认调用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");
    }
}

结果输出如下:

A static
Hello World!
B static
B not static
B constructor
A not static
A constructor

在实例化A类时,在顺序执行到非静态代码中的“B b = new B()”时,将先执行加载完毕B的所有代码;
如果我将代码改成“非静态代码块”在“实例化B代码”前,如下:

...
    {
        System.out.println("A not static");
    }
    B b = new B();
...

将会得到如下输出

A static
Hello World!
A not static//这一行也在前面了
B static
B not static
B constructor
A constructor

得以验证:同等级内代码逐条按顺序执行,遇到属性类则加载完属性类后再执行下一步代码
注意:当属性类不立刻进行实例化时,JVM只会将堆中的成员属性类指向一个null,而不进行加载该属性类的操作
例:

...
    B b;
...

代码如上时,将不会有任何与类B相关的输出

关联知识

类变量-static变量、类方法-static方法

这两者的特性:
- 全局唯一,一改都改,节省资源
- 可直接通过类名调用
- 仅能访问static数据、static方法
- 不能引用this和super
下面我们通过实例化对象执行步骤来推导出以上四条特性:

Created with Raphaël 2.1.0开始该类是否已加载到方法区中执行非静态代码1——在堆中为对象分配空间(对象信息:类引用、类变量引用、方法引用、成员变量等)执行静态代码——加载该类的静态信息(类、static代码、方法)——在方法区分配空间yesno

由流程图可知,
1. static代码只在类初始化时加载一次,加载后存在方法区,而每一个对象在实例化时,只是在堆中保存指向方法区的引用,所以全局唯一,一改都改,节省资源
2. 因为static代码在对象被实例化之前和类初始化一起执行,所以除了可以通过对象应用外,也可以直接通过类名引用
3. 因为先执行静态代码,再执行非静态代码,所以static代码仅能访问static数据、static方法
4. this、super属于非静态代码(??),所以不能引用this和super

你可能感兴趣的:(Java基础)