摘要:
在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。在实例化一个对象时,JVM首先会检查相关类型是否已经加载并初始化,如果没有,则JVM立即进行加载并调用类构造器完成类的初始化。在类初始化过程中或初始化完毕后,根据具体情况才会去对类进行实例化。本文试图对JVM执行类初始化和实例化的过程做一个简单地介绍,如果我的博文中存在错误或者您不认同的地方,欢迎通过评论来与我讨论,交流,拜谢。
执行步骤如下:
1:寻找类定义
jvm会在自己的一个名叫“方法区”的内存块中,寻找名叫“MyObject”的Class对象(注意class也是一个对象,该对象记录了所有类的定义),如果有,则按照Class对象的定义,生成一个MyObject对象。
2:加载类定义
如果“方法区”中没有名为“MyObject”的Class对象,jvm会用当前类的类加载器(classloader)从当前的classpath路径寻找名为"MyObject.class"的文件,如果找到,则将文件进行分析,转换为Class对象存放在“方法区”中,否则抛出“ClassNotFoundException”。对于jdk的class,jvm启动时,会用启动类加载器加载,对于用户的class,则会用应用程序类加载器实时加载,所谓实时加载,指的是遇到的时候再加载,而不是预先一次性加载。关于类加载器,有三级,jvm严格的限制了每一级的加载权限,加载模式为“双亲委托模式”,加载任何类,都先由父加载器加载。
3:给对象分配内存空间
找到MyObject的类定义后,jvm在内存“堆”中,开辟一个空间,该空间按照MyObject类定义开辟,并将该空间中的各个内存段设置默认值,对应的就是对象的属性初始化默认值。
4:对象的初始化顺序
对象的初始化都先从父类开始,顺序如下:给父类静态变量默认值 ;
对父类静态变量赋值 ;
执行父类静态块 ;----父类的类构造器<clinit>()
给当前类静态变量默认值 ;
对当前类静态变量赋值 ;
执行当前类静态块; ----子类的类构造器<clinit>()
给父类变量默认值 ;
对父类变量赋值 ;----父类的成员变量和实例代码块
执行父类构造函数; ----父类的构造函数
执行当前类构造函数;----子类的构造函数
运用举例:
- 局部变量:在方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化(分配内存)。成员变量可以被类中的方法和特定类的语句访问。
- 类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。static也是修饰符的一种。
我们在定义(声明)实例变量(成员变量)的同时,还可以直接对实例变量进行赋值或者使用实例代码块对其进行赋值。如果我们以这两种方式为实例变量进行初始化,那么它们将在构造函数执行之前完成这些初始化操作。
例如:
上面的例子正好印证了上面的结论。特别需要注意的是,Java是按照编程顺序来执行实例变量的初始化操作和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后面定义的实例变量。比如:
上面的这些代码是无法通过编译的,编译器会抱怨说我们使用了一个未经定义的变量。之所以要这么做是为了保证一个变量在被使用之前已经被正确地初始化。