当前位置:   article > 正文

类加载的过程_类加载过程

类加载过程

类加载

类加载的过程分为 5 个阶段:加载、验证、准备、解析、初始化。

图1
图1

以如下代码为例,对 Java 类加载的过程进行阐述。

  1. package com.zero.demo;
  2. public class SuperClassDemo {
  3. static {
  4. System.out.println("SuperClass Static Block!");
  5. }
  6. public static int VALUE = 11;
  7. private String str = "Hello World";
  8. private int value;
  9. public int getValue() {
  10. return value;
  11. }
  12. public void setValue(int value) {
  13. this.value = value;
  14. }
  15. }

加载

加载(Loading)是类加载(Class Loading)过程的其中一个阶段。

在加载阶段,JVM 需要完成 3 个步骤

◉ 通过类的全限定名来获取这个类的二进制字节流

◉ 将字节流转化为方法区的运行时数据结构。

◉ 在内存中生成一个代表这个类的 java.lang.Class 对象,作为这个类在方法区的访问入口。

以 SuperClassDemo 为例,加载阶段的过程,如下图所示:

图2
图2

不过,对于数组类而言,数组类本身不通过类加载器创建,它是由 JVM 直接在内存中动态构造出来的,但是数组的元素类型仍然需要依靠类加载器去创建。

 

验证

验证是连接阶段的第一步,目的是保证加载的字节码是合法的。

验证阶段包含 4 个动作:文件格式验证、元数据验证、字节码验证和符号引用验证。

  • 文件格式验证

验证字节流是否符合 Class 文件格式的规范,包括以下这些内容:

(1)是否以魔数 0xCAFEBABE 开头

(2)版本号是否跟 JVM 兼容

(3)检查常量池的常量tag标志

(4)……

  • 元数据验证

对字节码描述的信息进行语义分析,包括以下这些内容:

(1)是否有父类,除了 java.lang.Object,所有的类都有父类

(2)是否继承了不允许被继承的类,例如:final 修饰的类

(3)如果不是抽象类,是否实现了其父类或接口中的所有方法

(4)……

  • 字节码验证

验证程序语义是合法的、符合逻辑的,对类的方法体(Class 文件中的 Code 属性)进行校验分析。

例如:检验方法体的类型转换

List<String> list = new ArrayList<>();

  • 符号引用验证

验证类中引用的资源(外部类、方法、变量)是否存在,访问权限是否合法。

否则,JVM 会抛出 java.lang.IncompatibleClassChangeError 的子类异常,例如:

(1)java.lang.IllegalAccessError

(2)java.lang.NoSuchFieldError

(3)java.lang.NoSuchMethodError

(4)……

准备

准备是连接阶段的第二步,目的是为静态变量(被 static 修饰的变量)分配内存,初始化默认值。

Java 中所有基本数据类型的默认值,如下表所示:

类型默认值
int0
long0L
short(short)0
char'\u0000'
byte(byte)0
booleanfalse
float0.0f
double0.0d
referencenull

注:JVM 中 boolean 类型映射为 int 类型,false 用 0 表示,true 用 1 表示,由于 int 默认值为 0,因此 boolean 的默认值为 false。

以 SuperClassDemo 为例,VALUE 将会被初始化为 0。

  1. // 即使代码中 VALUE 赋值为 11,不过在准备阶段,VALUE 的值为 0
  2. public static int VALUE = 11;

不过,如果 VALUE 的代码改成如下所示,VALUE 将会被初始化为 11。

  1. // 这里涉及到 ConstantValue属性 的相关概念
  2. public final static int VALUE = 11;

解析

解析是连接阶段的第二步,目的是将接口、变量、方法的符号引用转换为直接引用。

  • 符号引用

以一组符号来描述所引用的目标,可以理解为,以文字形式来描述引用关系。

例如,SuperClassDemo 中的符号引用,通过 javap -verbose 命令打印,如下图所示:

  1. // 以这行代码为例,分析符号引用
  2. System.out.println("SuperClass Static Block!");
图3
图3

  • 直接引用

可以理解为,指向存放目标的内存地址的引用。

初始化

初始化是类加载过程的最后一个步骤,就是执行类构造器 <clinit>() 方法的过程。在此阶段,JVM 会执行执行类中编写的Java程序代码,对类的静态变量,静态代码块执行初始化操作。

例如,SuperClassDemo 中被 static 修饰的变量和代码块。

  1. // 按顺序执行
  2. // 将会打印出 “SuperClass Static Block!” ,而且只会打印一次
  3. static {
  4. System.out.println("SuperClass Static Block!");
  5. }
  6. // 在准备阶段,VALUE 的值为 0,在初始化阶段,VALUE 的值为 11
  7. public final static int VALUE = 11;

除此之外,<clinit> 的执行顺序是,父类的 <clinit> 方法总是在子类 <clinit> 方法之前被调用。

例如,ChildClassDemo 继承于 SuperClassDemo,如下所示:

  1. package com.zero.demo;
  2. public class ChildClassDemo extends SuperClassDemo {
  3. static {
  4. System.out.println("ChildClass Static Block!");
  5. }
  6. public static void main(String[] args) {
  7. ChildClassDemo child = new ChildClassDemo();
  8. }
  9. }

mian() 方法的执行结果,如下图所示:

图4
图4

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/291228
推荐阅读
相关标签
  

闽ICP备14008679号