JVM-类加载子系统-类加载器和类加载过程

先上类加载全过程的细节图:
在这里插入图片描述
1.其中类加载子系统只负责加载Class文件,至于是否可以执行交给执行引擎Execution Engine决定然而不是每一个Class文件都可以加载执行,只有符合格式规范的Class文件才可以被加载,Class文件开头有特定的标识CA FE BA BE可以被加载到JVM虚拟机里
2.加载后的类信息存放于一块称为 方法区的内存空间。除了类信息之外,方法区还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)

类加载器ClassLoader角色:
在这里插入图片描述

类的加载过程:
在这里插入图片描述
Loading模块的三个流程:
1.通过类Class.file的全限定类名获取此类的二进制字节流。
2.将该字节流所代表的静态存储解构转化为方法区的运行时数据结构。
3.在内存中生成一个代表这个类的java.lang.class对象,作为方法区这个类的各种数据的访问入口
———————————————————————————————————
Linking模块的三个流程:
verify验证阶段:
1.目的在于确保加载class文件的的字节流中所包含的信息符合JVM虚拟机的要求,保证加载类安全性,防止危害虚拟机本身,下图为合法的Class文件开头所包含的标识符。
2.主要的四种验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证。
在这里插入图片描述
———————————————————————————————————
prepare准备阶段:
1.在这个阶段为类变量分配内存并且赋值默认初始值,在init初始化阶段才会真正的赋值,例如,我在一个类里面定义了一个变量:
private static int a=1;
此时在prepare阶段分配内存的时候,a的值为0,在初始化阶段init的时候才会赋值为1。数据类型不一样,初始值也会不同,数值类型,整形的默认初始值为0;浮点型的为0.0 ;布尔类型为false;引用类型为null;
2.如果用final修饰的static,则上述不会赋值默认值,因为final在编译的时候就会分配了,准备阶段直接显式初始化。
3.prepare阶段不会为实例变量分配初始化,类变量会分配在方法区,而实例变量会随着对象一起分配到java的堆中。
———————————————————————————————————
reslove解析阶段:
1.将常量池内的符号引用转换为直接引用的过程。
2.事实上,解析操作往往会伴随着jvm在执行完初始化init之后再执行
3.符号引用就是一组符号来描述所引用的目标。符号应用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄
4.解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info/CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。
———————————————————————————————————
init阶段:
1.初始化阶段就是执行类构造器方法<client>()的过程–class init
2.此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
3.如图可以看出,构造器方法中的指令是有执行顺序的,根据右边的jclasslib插件不难看出,先赋值了1,随后再赋值了3,然后进行输出。同时,根据第三张图对比可以看出,如果没有用static修饰的话,在字节码文件中是不会有clinit的方法执行的,但是同样的是,任何一个类声明之后,内部至少都会有一个默认的继承自父类的init构造器。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那么,综合这一部分的内容,我们来看看这一段代码的输出结果是什么?

public class StacktruTest {
    private static int a = 1;

    static {
        a = 2;
        b = 20;
    }
    private static int b = 10;
    public static void main(String[] args) {
        System.out.println(a);
        System.out.println(b);
    }
}

我们来看看输出结果:
在这里插入图片描述
那么为什么b是10而不是20呢?原因就在于在linking的prepare阶段分配内存空间的时候,已经默认给b赋值=0(可以看看前面的prepare阶段),然后在init阶段重新赋值为20,因为static赋值是有顺序的,先赋值为20,在后面的private static int b = 10又重新给它赋值为10,所以这里输出的是10而不是20。我们来看下jclasslib里面的具体运行顺序:
在这里插入图片描述
4.若该类具有父类,jvm会保证子类的<client>()执行前,父类的<client>已经执行完毕。具体代码如下:

public class StacktruTest {
    //静态内部类
    static class father{
        //静态变量
        public static int A = 1;
      //静态代码块
        static{
          A = 2;
        }
    }

    static class son extends father{
        public static int B = A;
    }

    public static void main(String[] args) {
        //加载father类,其次加载son类,执行main方法,会将StacktruTest加载进内存,但在这之前会先加载object类。
        //加载完StacktruTest类后调用其静态方法,静态方法调用了son类的静态变量,此时会加载son类,但是在执行这一步操作之前
        //会先调用father类的clinit方法加载,然后调用son类的clinit的方法加载,此时父类的A已经赋值,所以输出为2.
        System.out.println(son.B);
    }
}

5.虚拟机必须保证一个类的<client>()方法在多线程下被同步加锁。演示如下:

public class StacktruTest {
    public static void main(String[] args) {
        Runnable r = () ->{
            System.out.println(Thread.currentThread().getName()+"开始");
            DeadThread deadThread = new DeadThread();
            System.out.println(Thread.currentThread().getName()+"结束");
        };

        Thread t1 = new Thread(r,"线程一");
        Thread t2 = new Thread(r,"线程二");
        t1.start();
        t2.start();
    }
}

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

            }
        }
    }
}

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值