JVM

30-类的加载过程(类的生命周期)

2022-04-29  本文已影响0人  紫荆秋雪_文

一、概述

Java中数据类型分为“基本数据类型”和“引用数据类型”。

二、过程一:加载(Loading)阶段

1、加载完成的操作

1.1、加载的理解
1.2、加载完成的操作

2、二进制流的获取方式

对于类的二进制数据流,虚拟机可以通过多种途径产生或获得。只要所读取的字节码符合JVM规范即可

3、类模型与Class实例的位置

3.1、类模型的位置
3.2、Class实例的位置
3.3、注意
    public void classMethod() {
        try {
            Class clazz = Class.forName("java.lang.String");
            //  获取当前运行时类声明的所有方法
            Method[] methods = clazz.getDeclaredMethods();

            for (Method m : methods) {
                //  获取方法的修饰符
                String mod = Modifier.toString(m.getModifiers());
                System.out.println(mod + " ");

                //获取方法的返回值类型
                String simpleName = m.getReturnType().getSimpleName();
                System.out.println(simpleName + " ");

                //获取方法名
                System.out.println(m.getName() + "(");
                Class<?>[] ps = m.getParameterTypes();
                if (0 == ps.length) {
                    System.out.println(")");
                }
                for (int i = 0; i < ps.length; i++) {
                    char end = (i == (ps.length - 1)) ? ')' : ',';
                    System.out.println(ps[i].getSimpleName() + end);
                }
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

4、数组类的加载

三、过程二:链接(Linking)阶段

1、链接(Linking)阶段之验证阶段(Verification)

1.1、格式验证
1.2、字节码的语义检查,但凡在语义上不符合规范的,虚拟机也不会给予验证通过
1.3、字节码验证,字节码验证也是“验证过程中最为复杂的一个过程”。它试图通过对字节码流的分析,判断字节码是否可以被正确的执行
1.4、符号引用的校验

2、准备

主要是为累的静态变量分配内存,并将其初始化为默认值
当一个类通过验证后,虚拟机就会进入准备阶段。在这个阶段,虚拟机就会为这个类分配相应的内存空间,并设置默认初始值。

2.1、注意
2.2、实例
public class LinkingTest {
    private static long id;
}
image.png
public class LinkingTest {
    private static final int num = 1;
    private static final String constStr = "CONST";
}
image.png

3、解析

将类、接口、字段和方法的符号引用转为直接引用

3.1、作用
3.2、小结
3.3、字符串

四、过程三:初始化(Initialization)阶段

为类的静态变量赋予正确的初始值

描述

Java编译器并不会为所有的类产生<clinit>()初始化方法。哪些类在编译Wie字节码后,字节码文件中将不会包含<clinit>()方法?

一个类中并没有声明任何的“类变量”,也没有静态代码块时
public class InitializationTest {
    public int id;

    public void add() {
        
    }
}
image.png
public class InitializationTest {
    // 字段
    public int id;
    // 定义类变量
    public static int id1 = 1;

    public void add() {

    }
}
image.png

public class InitializationTest {
    // 字段
    public int id;
    // 定义类变量
    public static int id1 = 1;
    public static int number;

    static {
        number = 2;
        System.out.println(number);
    }


    public void add() {

    }

}
image.png
一个类中声明类变量,但是没有明确使用类变量的初始化语句以及静态代码块来执行初始化操作时
public class InitializationTest {
    // 字段
    public int id;
    // 定义类变量
    public static int id1;
}
image.png
public class InitializationTest {
    // 字段
    public int id;
    // 定义类变量
    public static int id1;
    // 定义类变量,并显示赋值
    public static int id2 = 5;
}
image.png
一个类中包含static final修饰的基本数据类型的字段,这些类字段初始化语句采用编译时常量表达式
public class InitializationTest {
    // 字段
    public int id;
    // 定义类变量
    public static int id1;
    // 定义类常量
    public static final int id2 = 5;
}
image.png

1、static与final的搭配修饰的字段的显示赋值的操作,到底是在哪个阶段进行赋值

1.1、在链接阶段的准备环节赋值
public class InitializationTest {
    // 定义类常量
    public static final int id2 = 5;
    public static final String str = "CONST";
}
image.png
1.2、在初始化阶段<clinit>()中赋值
public class InitializationTest {
    // 定义类常量
    public static final int id2 = 5;
    public static final Integer id7 = 7;


    public static final String str = "CONST";
}
image.png
public class InitializationTest {
    // 定义类常量
    public static final int id2 = 5;
    public static final int id7 = new Random().nextInt(10);


    public static final String str = "CONST";
}
image.png
public class InitializationTest {
    // 定义类常量
    public static final int id2 = 5;

    public static final String str = "CONST";
    public static final String str2 = new String("CONST");

}
image.png

总结

在链接阶段的准备环节赋值的情况
在链接阶段的初始化阶段<clinit>()中赋值的情况

2、<clinit>()的线程安全性

3、类的初始化情况:主动使用 vs 被动使用

3.1、主动使用
3.1.1、当创建一个类的实例时,比如使用new关键字,或者通过反射、克隆、反序列化
3.1.2、当调用类的静态方法时,即当使用了字节码 invokestatic指令
3.1.3、当使用类、接口的静态字段时,比如,使用getstatic或putstatic。(对应访问变量、赋值变量操作)
3.1.4、当使用java.lang.reflect包中的方法反射类的方法是。比如Class.forName("com.lkty.loadandstore.InitializationTest")
3.1.5、当初始化子类时,如果发现其父类还没有进行过初始化,则需要先触发器父类的初始化。并不要求该类实现的接口也要先初始化
3.1.6、如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,该接口哟啊在其之前被初始化
3.1.7、当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类。JVM启动的时候通过移到类加载器加载一个初始类。这个类在调用public static void main(String[] arg)方法之前被链接和初始化。这个方法的执行将以此导致所需的类的加载、链接和初始化
3.1.8、当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。(涉及解析REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄对应的类)
3.2、被动使用
3.2.1、当访问一个静态字段时,只有真正声明这个字段的类才会被初始化。当通过子类引用父类的静态变量,不会导致子类初始化
3.2.2、通过数组定义类引用,不会触发此类的初始化
3.2.3、引用常量不会触发此类或接口的初始化。因为常量在链接阶段就已经被显式赋值了
3.2.4、调用ClassLoader类的loadClass()方法加载一个类,并不是对类的主动使用,不会导致类的初始化
上一篇下一篇

猜你喜欢

热点阅读