类加载机制中的【初始化】步骤解析

前言

我们知道系统加载.class文件分别有这几个步骤:加载连接初始化三个阶段。

其中加载阶段会通过全类名获得到对应文件的二进制字节流,再通过native方法产生一个Class对象,这一步主要由类加载器来完成。也就是说Class对象在第一步就产生了。

连接阶段又分为验证准备解析三个步骤。其中JVM会在准备阶段为类的所有静态变量赋上初值。

最后的初始化阶段,JVM会执行在编译时(javac命令生成.class时)生成的<clinit> () 方法,这个方法是用来初始化所有类的静态变量的,也就是说类的静态变量在这里才真正的初始化,之前的准备阶段并不会初始化静态变量,只是对静态变量赋初始值,比如int类型赋值为0,具体的值需要要初始化阶段才会赋上。

但是静态常量,即被final修饰的变量,会在准备阶段就进行初始化操作。

初始化步骤分析

根据上面的结论,我们知道初始化阶段其实就是初始化类静态变量的时期。

我们知道类名.classClass.forName("类名")都能得到对应类的Class对象,但是前者不会进行初始化操作,而后者会进行初始化操作。现在来看一个例子,体会这个差异:

import org.junit.Test;

public class MyTest {
    
    

    @Test
    public void test1(){
    
    
        System.out.println(Apple.class);
    }
    @Test
    public void test2() throws ClassNotFoundException {
    
    
        System.out.println(Class.forName("Apple"));
    }
}

class Apple {
    
    
    static {
    
    
        System.out.println("static init");
    }
}

test1输出如下:

test2输出如下:

可以发现类名.class由于不会进行初始化步骤,确实没有执行<cinit>()方法,走静态代码块。

Class.forName(String name, boolean initialize, ClassLoader loader)方法参数分析

我们知道Class.forName()方法其实还有一个重载方法,可以传入一个布尔值和一个ClassLoader,其实这个布尔值就是告诉JVM是否进行初始化步骤

在刚才的类中再添加test3:

@Test
    public void test3() throws ClassNotFoundException {
    
    
        System.out.println(Class.forName("Apple", false, Apple.class.getClassLoader()));
    }

执行该方法,输出如下:

注意到由于设置了false,这次forName方法并没有对Apple类进行初始化操作。

Main方法的初始化

其实如果是main()方法所在类的话,还有一些小细节值得注意。

假设有如下代码:

public class MyTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException {
    
    
    }
    public Apple apple(){
    
    
        return new Apple();
    }
}

运行main()方法,即使里面没有任何代码,如果我们在运行前把路径下的Apple.class文件删除,那么就会报如下错误:

可以注意到即使main()中不运行任何代码,但是也会去加载其所在类MyTest中引用的所有类,这里就是Apple类。并且这里的加载也不会执行初始化步骤

修改上面代码如下:

public class MyTest {
    
    
    public static void main(String[] args) throws ClassNotFoundException {
    
    
    	new Banana();
    }
}
class Banana{
    
    
    public Apple apple(){
    
    
        return new Apple();
    }
}

在运行前把路径下的Apple.class文件删除,发现不会报任何错误,程序正常退出。

由此可以得出JVM在运行main()所在类时,会将该类引用的所有类型的Class对象全部加载进内存。

猜你喜欢

转载自blog.csdn.net/weixin_55658418/article/details/130036224