5.JVM类的加载过程-加载,链接,初始化

 

1.类加载过程一:装载(Loading)

类加载分为加载、链接、初始化三步。
在这里插入图片描述
类的加载过程说明:
1.加载通过类的全限定名获取此类的二进制字节流。
2.将字节流所代表的静态存储结构转化层方法区的运行时数据结构。方法区在jdk7及以前称为永久代,JDK8及之后称为元空间。
3.在内存中生成一个大的Class对象。
在这里插入图片描述
加载class文件的方式有以下几种:
在这里插入图片描述

2.类加载过程二:链接(Linking)

链接分为验证、准备、解析三个小的阶段。
验证:
目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证虚拟机的安全。
验证过程主要包括:文件格式验证、元数据认证、字节码认证、符号引用认证。
如果验证不通过,会报一个验证出错的异常。
准备:
1.为类变量分配内存并且设置该类变量的默认初始值,即零值。例如int变量默认初始值为0,引用变量默认初始值为null。
例如在类Hello中有如下的一个变量定义:准备阶段会将a的默认初始值设置为0;在初始化阶段才会将a的值设置为1。

private static int a = 1;
  • 1

2.对于final修饰的static变量。在编译时期就已经分配空间了,在准备阶段会显示初始化。
3.准备阶段不会为实例变量分配初始化,因为这时候还没有创建对象。类变量分配在方法区,实例变量分配在堆区。
解析:
1.常量池的符号引用转换为直接引用的过程。
在这里插入图片描述

3.类加载过程三:初始化(Initialization)

在这里插入图片描述
1.初始化阶段就是执行类构造器方法<clinit>()的过程。
2.<clinit>()不是自己定义的,是javac编译器自动收集类中的所有类变量(指的是静态成员变量)的赋值动作和静态代码块中的语句合并而来。(也就是将类变量的初始化以及静态代码块中的语句集合在<clinit>()方法中)。
如果不存在static成员变量以及静态代码块,就不会生成<clinit>()方法。例如下面的代码,就不会生成<clinit>()
在这里插入图片描述
3.<clinit>()初始化的顺序是根据语句在源文件中出现的顺序决定的。例如下面的例子:number最终的值是10。在类加载的第二个阶段准备中,将number设置成了默认值0,然后再初始化阶段先设置成了20,再设置成了10。
在静态代码块中,可以给number赋值,但是不能去使用它。例如System.out.println(number);会报非法的前向引用错误。
在这里插入图片描述
4.<clinit>()不同于类的构造器,类的构造器是虚拟机视角下的<init>()方法。(java类默认都存在默认的构造方法,所以在虚拟机视角下,对于任何一个java类都会生成一个<init>()方法)。
5.若该类具有父类,JVM会保证子类的<clinit>()方法执行前先执行父类的<clinit>()方法。
例如下面的代码,Son继承了Fatcher类,所以会首先会加载Father类。加载Father类的过程,包含Loading,Linking,Initialization。在初始化阶段会生成并且执行Father类的<clinit>()方法,给A设置成了2,;然后再加载Son类,执行Son类的<clinit>()方法,将A的值设置成A。所以最终结果是2。
在这里插入图片描述
6.多线程情况下,一个类的<clinit>()方法是被同步加锁的。(一个类只会被加载一次)
例如下面的代码:定义了一个DeadThread类,里面有一个静态代码块,所以在初始化阶段会生成这个类的<clinit>()方法。
main函数中定义了两个线程,都会去调用DeadThread的static代码块, 所以两个线程都会去加载DeadThread类,要加载这个类,就会执行第三个初始化阶段的<clinit>()方法。

但是System.out.println(Thread.currentThread().getName() + "初始化当前类");只会被输出一次。这是因为<clinit>()方法被加了锁。

public class DeadThreadTest {
    public static void main(String[] args) {
        Runnable r = () -> { //lambda 表达式
            System.out.println(Thread.currentThread().getName() + "开始");
            DeadThread dead = new DeadThread(); //调用DeadThread的static代码块
            System.out.println(Thread.currentThread().getName() + "结束");
        };

        Thread t1 = new Thread(r,"线程1");
        Thread t2 = new Thread(r,"线程2");

        t1.start();
        t2.start();
    }
}

class DeadThread{
    static{
        if(true){
            System.out.println(Thread.currentThread().getName() + "初始化当前类");
            while(true){

            }
        }
    }
}

 

posted @ 2022-04-18 09:37  跃小云  阅读(142)  评论(0编辑  收藏  举报