在讲类的初始化的流程前,我们最好是能够先了解下java虚拟机的体系结构
java虚拟机内部体系结构(本图引自深入java虚拟机第二版)
首先,对上图中的几个区域,作个简单的介绍
一,类装载子系统:
我们都知道,java文件编译后生成的是一个class文件的字节码,而在系统运行时,要用到某个类时,java虚拟机将class文件
加载入系统,然后做一系列的工作,而加载的任务就是由类装载器子系统完成。
java虚拟机中有两种类装载器:启动类装载器和用户自定义类装载器,类的装载过程,不是这里的重点,所以,不作具体的描述。
二,运行时数据区
1,方法区
当虚拟机装载一个class文件后,java虚拟机将解析的类信息存在到方法区中,
2,堆
当程序运行时,虚拟机将所有程序在运行时创建的对象放入堆中
3,java栈
存储线程中java方法的调用状态,包括局部变量,调用的参数和返回值
4,pc寄存器
当每一格新线程被创建时,他都有一个自己的pc寄存器
5,本地方法栈
用于在调用本地方法时使用的的栈
从上面几点说明中,我们可以放心,方法区和堆是由java虚拟机实例所有线程共享的,而栈和pc寄存器是为单个线程所共享的
三,对象的构建与初始化流程
从上图我们可以看出,对象的构建与初始化主要有两部分组成,即类的准备工作,对象的构建与初始化
1,类的准备工作
我们知道,java中使用到一个类的方式有很多,例如创建一个对象(new A),使用类字面常量(A.Class),调用类的静态变量(A.a),
调用类的静态方法(A.f()),使用Class.forName("A"),那么在这几种情况下,什么时候才会导致出现类准备工作呢,以及执行哪类准备工作中的哪几步呢?
a,其实上在java中除了使用类字面常量(A.Class)以及访问类中“编译常量”时,只运行到类的装载荷类文件验证,
而不进行类变量的初始化外,其他的都会运行整个类准备工作
b,对于类“编译常量”,在分配内存时,会自动将其默认值设置为常量值
c,类变量的初始化和类静态语句块的执行,是按照其编写的顺序来执行的
d,对于继承类,先完成父类的类初始化流程再是子类的初始化流程
c,类的准备工作,只会在执行一次。
按照上面的几点说明, 我们可以看以下代码运行的结果
public class ParentClass { public static int a=10; public static int b=initb(); static { System.out.println("execute para static black.."); } public static final int c=11; public ParentClass(){ System.out.println("init ParentClass "); } public static int initb(){ System.out.println("execute parent initb.."); return 10; } public static void f3(){ System.out.println("execute parent f3"); } }
使用junit测试
public void testInit(){ System.out.println("ParentClass c is :" +ParentClass.c); System.out.println("ParentClass a is :" +ParentClass.a); System.out.println("ParentClass b is :" +ParentClass.b); }
输出结果如下:
ParentClass c is :11 execute parent initb.. execute para static black.. ParentClass a is :10 ParentClass b is :10
从结果中我们可以看出,由于静态成员域是c是一个“编译常量”所以在给类对象分配内存设置默认值时,c的默认就是 11 ,而不用等到初始化,
因此, 第一句输出时类还没进行初始化的过程,但在调用ParentClass.a时,由于a不时“编译常量”,所以要先进行类的初始化。
如果我们将测试换成
public void testInit(){ System.out.println("ParentClass c is :" +ParentClass.c); }
输出结果为:
ParentClass c is :11
我们可以看到,访问类的“编译常量”时,是不进行类的变量的初始话的。
而如果用
public void testInit(){ System.out.println("ParentClass a is :" +ParentClass.a); }
输出为:
execute parent initb.. execute para static black.. ParentClass a is :10
2,对象的创建以及初始化
如图所示,对象的创建流程 是1,给对象分配空间并设置默认值-〉2,初始化变量及执行非静态域-〉3,执行构造函数-〉4,返回对象的引用
我们先从一个例子中来看对象的构建及初始化
public class TestClass { public int a=10; { System.out.println("execute black..."); } public int b=initb(); public int initb(){ System.out.println("execute testClass init..."); return 110; } public TestClass(){ System.out.println("execute TestClass init..."); } }
测试结果:
new TestClass(); //输出 execute black... execute testClass init... execute TestClass init...
从上面的输出, 我们可以反映出,对象成员域的初始话是在构造函数之前执行的。
3,关于继承类对象的构建与初始化
a,前面我们指出类变量的初始化,永远在对象变量及对象的的构造之前完成。而对继承的类的类变量,是先执行类父类变量的初始化
b,对于对象的的构建及初始化流程为
先执行父类成员的初始化,再是父类的构造函数
再执行之类的子类的成员域的初始化,再试子类的构造函数
具体流程如下: