Java类的加载过程

3 篇文章 0 订阅

让我们从一道面试题说起,请问以下代码的执行结果是什么:

public class Test{
	static {
		A = 0;
	}
	static int A = 1;
	public static void main(String[] args){
		System.out.println(Test.A);
	}
}

编译错误?0?1?
正确答案是1。

为什么A在定义之前赋值不报错呢?这就要从类的加载过程说起。

一个类的生命周期分为以下几个阶段:
加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using、卸载Unloading

其中,验证、准备、解析被统称为连接Linking

以上七个阶段中,加载、验证、准备、初始化、卸载这五个阶段的顺序是确定的,它们会依次开始,但可能交叉进行,即会在一个阶段执行过程中,调用、激活另一个阶段。
而解析的顺序则不确定,在某些情况下会在初始化之后才开始,这是为了支持Java语言运行时绑定特征。运行时绑定在后面介绍解析时再细讲。

下面依次介绍类加载的各个阶段。

  • 加载

加载过程完成三件事

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

通过类名获取字节流,一般是从class文件获取,但也可以从jar包、网络、jsp文件、数据库中获取,甚至可以在运行时计算生成。这个特点让java的灵活性大幅提升,使其能够适应各种技术场景。
关于第二点,class的字节流存储是按照约定的格式存储的,但是程序运行时,会将其转换为特定的数据结构。
关于方法区,jdk8之后改为元空间。是虚拟机用来存放类信息、常量、静态变量的内存区域。java运行时内存区域基本结构如下:
在这里插入图片描述

  • 验证

加载完之后,需要对类信息进行验证。以免一些恶意的代码对虚拟机的攻击导致虚拟机崩溃。

  • 准备

为static变量在方法区分配内存,并将其初始化为零值。
如:本文开头的例子中,static int A = 1;
此时仅会执行static int A; 并且为A赋默认零值。
而为A赋值为1的过程会在初始化阶段完成。
因为准备阶段先执行,所以即便从代码位置来说,静态代码块A=0;在A定义之前,但虚拟机加载类时,其实先执行的是静态变量的初始化,所以静态代码块A=0;并没有报错。

  • 解析

解析是将类信息中的符号引用替换为直接引用的过程。符号引用是java代码编译之后以特定格式存储在class文件中的一组符号。直接引用是指运行期间可以直接指向目标的指针或间接指向目标的句柄。

解析过程可能发生在初始化之前,也可能发生在初始化之后运行期间。这是因为某些符号引用只有在运行时才能确定其实际调用的对象。例如通过接口调用实现类的方法,对接口的初始化时,并不知道是哪个类实现了此接口的方法,所以无法将符号引用转换为直接引用。只能到运行时,实例化了对象之后,根据具体对象再进行解析。还有通过父类引用指向子类的实例时,也是在运行期间才能确定方法的直接引用。也就是所谓的动态绑定。
动态绑定是JAVA实现多态的重要因素。

初始化

初始化是将代码中的静态代码块按顺序提取合并,生成一个叫做<clinit>的类构造器,并执行该构造器的过程。
如文章开头的例子中,类构造器应该包含以下两行代码:
A = 0;
A = 1;
因为类构造器的执行过程是在准备阶段后执行的,所以虽然A = 0;在A定义之前出现,但是实际执行时,A定义是在准备阶段进行的,所以编译不会报错。虽然在A定义之前的静态块中可以为其赋值,但是却不能访问。如下代码编译会失败:

public class Test{
	static {
		A = 0;
		System.out.print(A); //此行会编译失败
	}
	static int A = 1;
}

虚拟机会保证<clinit>方法执行前,父类的<clinit>已经执行完毕。因此父类中的静态代码块会在子类初始化前执行完毕。

且虚拟机会保证在多线程同时初始化一个类的情况下,只有一个线程会进入<clinit>方法,其他线程则会阻塞等待。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值