在了解java初始化直接我们先看下图
上图描述了java类从装载到卸载的整个生命周期。
而类的初始化穿插在准备、初始化两个过程,下文将分开展示各个阶段所做工作。
一:准备
在准备阶段JVM为类变量分配内存,根据类型设置变量的初始值。
基本类型 | 默认值 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
short | (short)0 |
byte | (byte)0 |
boolean | false |
char | \u0000 |
reference | null |
二:初始化
Java 编译器把所有的类变量初始化语句和类的静态初始化器通通收集到 类初始方法<clinit> 方法内,该方法只能被 Jvm 调用,专门承担初始化工作。
初始化类时主要包含一下两个工作:
(1):如果类存在父类,且父类没有被初始化,先初始化父类。
注意:初始化父类时第一被初始化的类永远是object类,父类总在子类之前被初始化。
(2):如果类存在一个初始化对象,就执行该方法。
除接口以外,初始化一个类之前必须保证其直接超类已被初始化,并且该初始化过程是由 Jvm 保证线程安全的。另外,并非所有的类都会拥有一个 <clinit>() 方法,在以下条件中该类不会拥有 <clinit>() 方法:
- 该类既没有声明任何类变量,也没有静态初始化语句;
- 该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句初始化;
- 该类仅包含静态 final 变量的类变量初始化语句,并且类变量初始化语句是编译时常量表达式。
注意:<clinit>() 方法类变量初始化语句和类型的静态初始化语句与在类中声明的顺序一致
类初始化后,即可调用它的静态方法,或者创建实例。
三:对象实例化
在类被装载、连接和初始化,这个类就随时都可能使用了。对象实例化和初始化是就是对象生命的起始阶段的活动。
Java 编译器在编译每个类时都会为该类至少生成一个实例初始化方法--即 "<init>()" 方法。此方法与源代码中的每个构造方法相对应,如果类没有明确地声明任何构造方法,编译器则为该类生成一个默认的无参构造方法,这个默认的构造器仅仅调用父类的无参构造器,与此同时也会生成一个与默认构造方法对应的 "<init>()" 方法.
通常来说,<init>() 方法内包括的代码内容大概为:调用另一个 <init>() 方法;对实例变量初始化;与其对应的构造方法内的代码。
如果构造方法是明确地从调用同一个类中的另一个构造方法开始,那它对应的 <init>() 方法体内包括的内容为:一个对本类的 <init>() 方法的调用;对应用构造方法内的所有字节码。
如果构造方法不是通过调用自身类的其它构造方法开始,并且该对象不是 Object 对象,那 <init>() 法内则包括的内容为:一个对父类 <init>() 方法的调用;对实例变量初始化方法的字节码;最后是对应构造子的方法体字节码。
如果这个类是 Object,那么它的 <init>() 方法则不包括对父类 <init>() 方法的调用。
注意java解释器在生成class文件时会把所有的类块放到构造方法里面。
如类
public class Normal
{
public Normal ()
{
System.out.println("no param construct");
}
{
System.out.println("class filed");
}
static{
System.out.println("Static");
}
public Normal ( String test )
{
System.out.println("param construct");
}
}
生成的字节码文件如下。
// Compiled from Normal.java (version 1.6 : 50.0, super bit)
public class Normal {
// Method descriptor #6 ()V
// Stack: 2, Locals: 0
static {};
0 getstatic java.lang.System.out : java.io.PrintStream [8]
3 ldc <String "Static"> [14]
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [16]
8 return
Line numbers:
[pc: 0, line: 12]
[pc: 8, line: 1]
// Method descriptor #6 ()V
// Stack: 2, Locals: 1
public Normal();
0 aload_0 [this]
1 invokespecial java.lang.Object() [25]
4 getstatic java.lang.System.out : java.io.PrintStream [8]
7 ldc <String "class filed"> [27]
9 invokevirtual java.io.PrintStream.println(java.lang.String) : void [16]
12 getstatic java.lang.System.out : java.io.PrintStream [8]
15 ldc <String "no param construct"> [29]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [16]
20 return
Line numbers:
[pc: 0, line: 4]
[pc: 4, line: 9]
[pc: 12, line: 6]
[pc: 20, line: 7]
Local variable table:
[pc: 0, pc: 21] local: this index: 0 type: Normal
// Method descriptor #21 (Ljava/lang/String;)V
// Stack: 2, Locals: 2
public Normal(java.lang.String test);
0 aload_0 [this]
1 invokespecial java.lang.Object() [25]
4 getstatic java.lang.System.out : java.io.PrintStream [8]
7 ldc <String "class filed"> [27]
9 invokevirtual java.io.PrintStream.println(java.lang.String) : void [16]
12 getstatic java.lang.System.out : java.io.PrintStream [8]
15 ldc <String "param construct"> [33]
17 invokevirtual java.io.PrintStream.println(java.lang.String) : void [16]
20 return
Line numbers:
[pc: 0, line: 15]
[pc: 4, line: 9]
[pc: 12, line: 17]
[pc: 20, line: 18]
Local variable table:
[pc: 0, pc: 21] local: this index: 0 type: Normal
[pc: 0, pc: 21] local: test index: 1 type: java.lang.String
}
注意字节码的Normal(), Normal(java.lang.String test),它们都包含了类块
{
System.out.println("class filed");
}
相应的字节码,并在相应构造方法代码的最前面。
实例说明:
1:普通类初始化顺序
对象初始化时先初始化静态块,然后方法块,最后构造方法
class Normal
{
public Normal ()
{
System.out.println("no param construct");
}
{
System.out.println("class filed");
}
static{
System.out.println("Static");
}
public Normal ( String test )
{
System.out.println("param construct");
}
}
public class test{
public static void main( String args[] ){
new Normal();
}
}
调用输出结果
Static
class filed
no param construct
分析 静态块 在初始化阶段即完成赋值与操作 第一输出 Static
类块 在生成class文件已经锲入到 构造方法里,并且是放在构造方法执行的最前面, 第二个输出为 class filed
构造方法 类实例化阶段调用
2: 继承类初始化
class Normal extends normalParent {
public Normal ()
{
System.out.println("no param construct");
}
{
System.out.println("class filed");
}
static{
System.out.println("Static");
}
public Normal ( String test )
{
System.out.println("param construct");
}
}
class normalParent{
static{
System.out.println("parnet Static");
}
{
System.out.println("parent class filed");
}
normalParent(){
System.out.println("parnet construct");
}
}
public class test{
public static void main( String args[] ){
new Normal();
}
}
输出结果
parnet Static
Static
parent class filed
parnet construct
class filed
no param construct
结果分析
Normal 初始化阶段,JVM调用Normal的<clinit>() 方法时,先调用Normal父类的normalParent <clinit>() 方法【即初始化父类的静态化块】:第一输出 parnet Static
调用完Normal父类的normalParent <clinit>() 方法在嗲用Normal的<clinit>() 方法【即初始化Normal的静态块】:第二输出Static
Normal 实例化时调用构造方法是,先调用父类的构造方法。第三 输出 parent class filed,第四输出 parnet construct
最后调用Normal的构造放哪规范。第五 输出 class filed,第六输出no param construct
总结
Java初始化的顺序如下:
父类静态初始化---->子类静态初始化---->父类初始化块---->父类构造方法---->子类初始化块---->子类构造方法。