Java 类加载过程之初始化

类初始化

字节码角度分析类初始化过程

init 方法

看看空的类的

package com.chi.jvm.clazz;
public class InitTest1 {
          
   
}

0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
4 return

编译器会自动为类加上一个无参构造器,并调用Object父类的无参构造方法

package com.chi.jvm.clazz;

public class InitTest1 {
          
   
    private  int a = 1;
}

// 调用父类构造器
0 aload_0
1 invokespecial #1 <java/lang/Object.<init> : ()V>
// 初始化实例成员属性
4 aload_0
5 iconst_1
6 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>
9 return

成员变量在默认的构造器方法中初始化,但可以注意到调用父类方法的指令始终放在前面

package com.chi.jvm.clazz;

public class InitTest1 {
          
   
    private  int a;
    public InitTest1(){
          
   
        a = 2;
    }
}


// 调用父构造
 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init> : ()V>
 
 // 初始化为 1
 4 aload_0
 5 iconst_1
 6 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>

 // 赋值为 2
 9 aload_0
10 iconst_2
11 putfield #2 <com/chi/jvm/clazz/InitTest1.a : I>
14 return
package com.chi.jvm.clazz;
public class InitTest1 {
          
   
    private  int a;
    public InitTest1(){
          
   
        System.out.println(a);	// 输出 0
        a = 2;
    }
}

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init> : ()V>
 4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
 7 aload_0
 8 getfield #3 <com/chi/jvm/clazz/InitTest1.a : I>
11 invokevirtual #4 <java/io/PrintStream.println : (I)V>
14 aload_0
15 iconst_2
16 putfield #3 <com/chi/jvm/clazz/InitTest1.a : I>
19 return

如果实例属性字段声明阶段没有初始化,在字节码层面上不会添加默认的初始值,但是通过sout能够正常打印,说明默认值是硬编码到字节码文件中的。

clinit 方法

package com.chi.jvm.clazz;
public class ClinitTest1 {
          
   
    private static int a;
}

// 无clint方法

静态字段

package com.chi.jvm.clazz;
public class ClinitTest1 {
          
   
    private static int a = 1;
}

// clint
0 iconst_1
1 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
4 return

静态代码块

package com.chi.jvm.clazz;

public class ClinitTest1 {
          
   
    private static int a = 1;
    static {
          
   
        a = 2;
    }
    static {
          
   
        a = 3;
    }
}

// clinit 方法
 0 iconst_1
 1 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
 4 iconst_2
 5 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
 8 iconst_3
 9 putstatic #2 <com/chi/jvm/clazz/ClinitTest1.a : I>
12 return
package com.chi.jvm.clazz;

public class ClinitTest2 {
          
   

    static {
          
   
        System.out.println("1");
    }

    static {
          
   
        System.out.println("2");
    }

    public ClinitTest2(){
          
   
        System.out.println(3);
    }

    public static void main(String[] args) {
          
   
        new ClinitTest2();
        new ClinitTest2();
    }
}

/*
1
2
3
3
*/

静态属性和静态代码块只会初始化一次

final 关键字修饰的字段

    修饰实例属性:必须在声明时初始化,或在构造方法中初始化 修饰静态属性:必须在声明时初始化,或在static静态代码块中初始化

在构造方法中调用其他构造方法

    调用自己:this() 调用父类:super() 无论调用哪个构造器,必须放在所有语句之前

总结

    编译器会提供默认的构造方法,用于调用父类构造方法和初始化实例属性 编译器会提供clinit方法初始化静态属性、按顺序执行静态代码块 在构造器中调用其他构造器方法必须放在第一句 final修饰的属性必须进行初始化
经验分享 程序员 微信小程序 职场和发展