JVM类加载器、代码初始化执行顺序

本文深入探讨Java中类的初始化过程,包括静态代码块的执行条件、子类与父类初始化顺序,以及类加载器的工作流程。通过具体示例,解析了哪些操作会被视为主动调用,从而触发类的初始化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)下面第一种和第二种会初始化A执行它的static里面的代码块,但是第三种不会,主要原因就在于第三种情况访问的A的静态变量是静态常量,所以虽然是主动调用了A,但是不会去初始化A,这算是静态常量的特殊性。JVM01是入口类,所以它的静态代码块是肯定要执行的。

public class JVM01 {
    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(A.x);
    }
}

class A {
//    static final int x = new Random().nextInt(100);
//    static int x = 10;
    static final int x = 10;

    static {
        System.out.println("static A block");
    }
}

(2)一般情况下,主动调用子类,会先初始化父类;反之主动调用父类的话,不会初始化子类,否则主动调用Object的话,所有的类都要初始化了。当然,如果父类已经被主动调用并初始化过了,再主动调用子类,就不会再去初始化父类了,前提是在同一个类加载器中。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(Child.y);
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }
}

class Child extends Parent{

    static int y = 20;

    static {
        System.out.println("static child block");
    }
}

结果是:

static main block
static parent block
static child block
20

(3)前面说主动调用子类会先初始化父类,这个主动调用时有条件的,必须调用的是子类自己的静态变量或静态方法,意思是说必须在自己的类里面定义的,如果是父类里面定义的,虽然能调用,但因为不是在自己类里面定义,所以调用时不能算是主动调用,所以不会初始化子类,而是直接初始化这个静态变量或静态方法定义所在的父类。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args){
        System.out.println(Child.x);
        Child.doSomething();
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }

    static void doSomething(){
        System.out.println("do something");
    }
}

class Child extends Parent{

    static int y = 20;

    static {
        System.out.println("static child block");
    }
}

结果是:

static main block
static parent block
10
do something

(4)哪些算是主动调用?

  1. 创建类的实例,即new一个,如果只是Parent parent;还不算,需要parent = new Parent();时才算是创建。
  2. 访问某个类的静态变量(除final常量)或静态方法,或者对静态变量赋值。
  3. 反射,就是`Class.forName(“xxx.xxx”)。
  4. 初始化它的子类,如果它没被初始化过,也算是主动调用了它进行初始化。
  5. 启动类。

(5)自然的,其他情况不算是主动调用,不会初始化。比如使用ClassLoader去加载类的话,就不算主动调用,也就不会初始化。

public class JVM01 {

    static {
        System.out.println("static main block");
    }

    public static void main(String[] args) throws ClassNotFoundException {

        ClassLoader classLoader = ClassLoader.getSystemClassLoader();

        Class<?> clazz = classLoader.loadClass("Parent");

        System.out.println("=============");

        clazz = Class.forName("Parent");
    }
}

class Parent {

    static int x = 10;

    static {
        System.out.println("static parent block");
    }
}

结果是:

static main block
=============
static parent block

(6)类加载器工作流程

  1. 类的加载。把.class文件中的为禁止数据读取到内存中,放在内存的方法区,并且在堆区创建一个相应的java.lang.Class对象,用来封装类在方法区里的数据结构。
  2. 类的连接。连接主要有3个步骤,一个是验证,就是检查是否满足一些java标准或者检查引用之间的正确性等;二是准备,主要是为静态变量分配内存,并且初始化默认值;三是解析,主要是把符号引用转化成直接引用,也就是说如果在一个类里面有另一个类方法的引用,那么就会把这行代码直接替换成一个指针,这个指针指向这另一个类方法在内存中的地址,这就是转成直接引用。
  3. 类的初始化。这就是根据代码中的定义来给静态变量赋值。

也就是说,我们的静态变量有可能要经过两次赋值,第一次是赋默认值,第二次是赋值我们写的值。赋值我们自己写的值也是依次从上往下来执行标记有static的代码。

public class JVM01 {

    public static void main(String[] args) throws ClassNotFoundException {
        Parent parent = Parent.getInstance();
        System.out.println(Parent.x);
        System.out.println(Parent.y);
    }
}

class Parent {

    static Parent singleton = new Parent();

    static int x = 10;

    static int y;

    private Parent(){
        x++;
        y++;
    }

    public static Parent getInstance(){
        return singleton;
    }
}

上面代码,当调用getInstance的时候就算是主动调用了,所以开始初始化,初始化有顺序,所以先执行第一行,也就是一个构造函数,xy都赋值为1,然后执行第二行和第三行,把x重新赋值了10,y不变,所以最终结果是:

10
1

如果静态代码顺序换一下,如下:

static int x = 10;

static int y;

static Parent singleton = new Parent();

那么结果就是:

11
1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值