您的位置:首页 > 职场人生

黑马程序员——Java程序的初始化过程详解

2015-08-17 18:05 525 查看
------<a href="http://www.itheima.com"target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流!
-------

    Java程序的初始化涉及到内存分配和对Java中的变量的赋值,弄清的初始化过程有助于理解Java的运行原理,这样当程序的运行结果和直觉不一样时才能找到原因。

    一、变量初始化

        Java中的变量从大的方面上分为成员变量和局部变量,而成员变量又分为类变量和实例变量。类体内定义的变量是成员变量,被static修饰的成员变量是类变量,类变量从属于类,内存中一个类的类变量只占用一份内存空间;类中没有被static修饰的变量是实例变量,实例变量从属于该类的每个对象,每当创建一个该类的对象时实例变量就要占用一份内存。

   Java中除了类变量外剩下的都是局部变量,包括方法的形式参数、方法内部定义的变量、语句块中的变量。Java确保所有的变量在使用前都能得到恰当的初始化,局部变量也不例外,对于局部变量来说,Java以编译时错误的形式确保其被初始化,并且Java程序的嵌套语句块中不能出现同名的局部变量。Java在程序运行时能保证所有的成员变量都能得到一个初始化值,对基本类型来说是和该类型对应的0,对引用类型来说是null,对布尔类型来说是flase。

     1 实例变量的初始化时机

   实例变量从属于对象,每生成一个类的对象时,虚拟机都要为实例变量分配内存。在源代码的层次来看,程序可以在以下几个地方对实例变量执行初始化:

定义实例变量时指定初始值
非静态初始化语句块中指定实例变量的初始值
构造器中指定实例变量的初始值
   以上几种方式中,前两种比最后一种更早执行,并且他们的初始化顺序与其在源代码中的出现顺序一致。其实这三种初始化方式的地位都是一样的,这是因为Java源代码被编译成字节码时,这三种初始化方式的语句都会被移动到一个名为init的构造方法中。只不过,定义变量时得到的赋值语句位于init方法的最前面,从语句块中得到的赋值语句次之,源代码构造器中的赋值语句放在最后面。

    2 类变量的初始化时机

      Java虚拟机加载一个类的.class文件时,当一个类被加载进内存时JVM会创建一个Class类的对象来表示该类,由于每个类在内存中都只有一个与该类相关的Class对象,又因为类变量从属于类,所以JVM只为类变量初始化一次并分配一次内存。从源代码的层次看,成员可以在两个地方初始化类变量:

定义类变量时指定初始值
在静态初始化块中初始化类变量
    每当一个类被加载进内存后,JVM先为所有类变量分配存储空间,然后再按源代码中定义时初始化语句和静态初始化块宏的初始化语句的顺序为类变量执行初始化。

  二  继承与初始化

    当初始化涉及到继承时,过程会变的很复杂,但了解了整个初始化的过程,对初始化有全局的了解是很有益处的。

    1 对象创建过程

    初始化的顺序是先静态成员,然后是非静态成员。静态初始化只有在必要的时刻才会进行,也就是类的对象被创建时或是第一次访问类的静态数据时。下面以创建一个Person类的对象为例,来说明对象创建过程中所发生的一系列事件:

即使Person类中没有显式的静态成员,或静态成员没有被调用,由于类的构造器是静态的方法,而对象的创建过程中构造器必定会被调用,不管类中有没有显式的构造器,所以当首次创建Person类的对象时,JVM必定会查找类路径,以确定Person.class文件的位置。
接着JVM调用某个类加载器将存储在某个位置的Person.class文件装在进内存中,这时会创建Person类对应的Class类的对象,然后类的静态变量会被依次初始化,并且静态初始化只在类第一次被加载时进行。
再接着用创建Person类的一个对象,并在堆上为该对象分配足够的空间,然后将该空间清零。
最后执行实例变量的初始化操作,先执行变量定义处和非静态块中的初始化,再执行构造器中的初始化。

   2 涉及继承的初始化

    下面以一个启动类为起点的一系列过程为例,来对涉及继承的初始化作简单的介绍。

class Person
{
private int i = 8;
protected int j;
Person()
{
System.out.println("i = "+i+ ",j = "+ j);
j = 38;
}
private static int p1 =
printInit("static Person.p1 initialized");
static int printInit(String s)
{
System.out.println(s);
return 40;
}
}

public class Student extends Person
{
private int k = printInit("Student.k initialized");
public Student()
{
System.out.println("k = "+k);
System.out.println("J = "+j);
}
private static int p2 =
printInit("static Student.p2 initialized");
public static void main(String[] args)
{
System.out.print("Student constructor");
Student s = new Student();
}
}

运行结果:

static Person.p1 initialized
static Student.p2 initialized
Student constructori = 8,j = 0
Student.k initialized
k = 40
J = 38

     Student类作为启动类启动了JVM,所发生的第一件事是调用Student类的主方法(main方法,一个静态方法),于是JVM寻找Student类的字节码。在加载Student类时,虚拟机注意到该类有一个父类,于是就先加载它的父类。假如Person还有父类,JVM会继续去加载。接着根基类中的static变量的初始化开始执行,然后是下一个子类的初始化静态变量,依次类推。

     当必要的类都加载完毕后,就可以创建对象了。接下来的过程和上一小节的对象的创建过程类似,不同之处在于:当堆内存清零后,根基类的定义初始化语句和构造器会先被调用,接着是下一个类的。

   

        
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息