类加载的过程

类加载

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

图1
图1

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

package com.zero.demo;

public class SuperClassDemo {

    static {
        System.out.println("SuperClass Static Block!");
    }

    public static int VALUE = 11;
    
    private String str = "Hello World";

    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

加载

加载(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。

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

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

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

解析

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

  • 符号引用

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

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

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

  • 直接引用

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

初始化

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

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

// 按顺序执行

// 将会打印出 “SuperClass Static Block!” ,而且只会打印一次
static {
    System.out.println("SuperClass Static Block!");
}

// 在准备阶段,VALUE 的值为 0,在初始化阶段,VALUE 的值为 11
public final static int VALUE = 11;

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

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

package com.zero.demo;

public class ChildClassDemo extends SuperClassDemo {
    static {
        System.out.println("ChildClass Static Block!");
    }
    
    public static void main(String[] args) {
        ChildClassDemo child = new ChildClassDemo();
    }
}

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

图4
图4

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值