java基础整理之--类的初始化

 在讲类的初始化的流程前,我们最好是能够先了解下java虚拟机的体系结构

                  java基础整理之--类的初始化

                                             java虚拟机内部体系结构(本图引自深入java虚拟机第二版)

 

首先,对上图中的几个区域,作个简单的介绍

一,类装载子系统:

      我们都知道,java文件编译后生成的是一个class文件的字节码,而在系统运行时,要用到某个类时,java虚拟机将class文件

加载入系统,然后做一系列的工作,而加载的任务就是由类装载器子系统完成。

      java虚拟机中有两种类装载器:启动类装载器和用户自定义类装载器,类的装载过程,不是这里的重点,所以,不作具体的描述。

 

二,运行时数据区

《深入java虚拟机》写道
当java虚拟机运行一个程序时,它需要内存来存储许多的东西,例如,字节码,从已装载class文件中得到的其他信息,程序创建的对象,

传递给方法的参数,返回值,局部变量,以及运算的中间结果等等。java虚拟机把这些东西都组织到几个“运行时数据区”中,便于管理

 

      1,方法区 

           当虚拟机装载一个class文件后,java虚拟机将解析的类信息存在到方法区中,

      2,堆

           当程序运行时,虚拟机将所有程序在运行时创建的对象放入堆中

      3,java栈

           存储线程中java方法的调用状态,包括局部变量,调用的参数和返回值

      4,pc寄存器

           当每一格新线程被创建时,他都有一个自己的pc寄存器

      5,本地方法栈

           用于在调用本地方法时使用的的栈

      从上面几点说明中,我们可以放心,方法区和堆是由java虚拟机实例所有线程共享的,而栈和pc寄存器是为单个线程所共享的

 

三,对象的构建与初始化流程

                                         java基础整理之--类的初始化

    从上图我们可以看出,对象的构建与初始化主要有两部分组成,即类的准备工作,对象的构建与初始化

    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,对于对象的的构建及初始化流程为

         先执行父类成员的初始化,再是父类的构造函数

         再执行之类的子类的成员域的初始化,再试子类的构造函数

   具体流程如下:

   java基础整理之--类的初始化

你可能感兴趣的:(java,C++,c,虚拟机,C#)