1.加载

“加载”(Loading)阶段是整个“类加载”(Class Loading)过程中的一一个阶段,希望读者没有混淆这两个看起来很相似的名词。在加载阶段,Java虛拟机需要完成以下三件事情:

  • 1)通过一个类的全限定名来获取定义此类的二进制字节流。
  • 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

类被加载到方法区中后主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的 引用、对应class实例的引用等信息。 类加载器的引用:这个类到类加载器实例的引用对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。

Java类的加载过程_Java类-IT乾坤技术博客 (itqiankun.com)

  • 简洁来说
    • 一个Java文件从编码完成到最终执行,一般主要包括两个过程:编译和运行,其中编译就是把我们写好的java文件,通过javac命令编译成字节码,也就是我们常说的.class文件,然后运行则是把编译声称的.class文件交给Java虚拟机(JVM)执行。而我们所说的类加载过程即是JVM虚拟机把.class文件中类信息加载进内存,并将数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的 对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点。
    • 举个通俗点的例子来说,JVM在执行某段代码时,遇到了Class A, 然而此时内存中并没有Class A的相关信息,于是JVM就会到相应的Class文件中去寻找class A的类信息,并加载进内存中,这就是我们所说的类加载过程。由此可见,JVM不是一开始就把所有的类都加载进内存中,而是只有第一次遇到某个需要运行的类时才会加载,且只加载一次。

image.png

2.连接

2.1验证

验证的目的

主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
1.包括对于文件格式的验证,比如常量中是否有不被支持的常量?文件中是否有不规范的或者附加的其他信息?
2.对于元数据的验证,比如该类是否继承了被final修饰的类?类中的字段,方法是否与父类冲突?是否出现了不合理的重载?
3.对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。
4.对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?

验证做的具体内容

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作:

1.文件格式验证:验证字节流是否符合Class文件格式的规范;
例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。
2.元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了 java.lang.Object之外。
3.字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的,如循环、分支等
4.符号引用验证:确保解析动作能正确执行,比如不能访问引用类的私有方法、全限定名称是否能找到相关的类。

2.2准备

在准备阶段,为静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认为静态变量的初值是这样的

1.静态变量是基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0
2.静态变量时引用类型的默认值为null
3.被final和static共同修改的静态变量,我们通常称为常量,然后常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段 中a的初值就是100。

2.3解析

这一阶段的任务就是把常量池中的符号引用转换为直接引用
什么是符号引用,什么是直接引用?

1.符号引用:即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
2.直接引用:可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;
3.在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
举个例子来说,现在调用方法hello(),这个方法的地址是1234567,那么hello就是符号引用,1234567就是直接引用。

3.初始化阶段

类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:

– 创建类的实例,也就是new的方式
– 访问某个类或接口的静态变量,或者对该静态变量赋值
– 调用类的静态方法
– 反射(如Class.forName(“com.shengsiyuan.Test”))
– 初始化某个类的子类,则其父类也会被初始化
– Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类

初始化顺序
  1)如果这个类还没有被加载和链接,那先进行加载和链接
  2)假如这个类存在直接父类,并且这个类还没有被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)
  3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
  4)总的来说,初始化顺序依次是:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器;

如果有父类,则顺序是:父类static方法 –> 子类static方法 –> 父类构造方法- -> 子类构造方法

详细过程:

Java类的加载过程_Java类-IT乾坤技术博客 (itqiankun.com)